-
Notifications
You must be signed in to change notification settings - Fork 1
DSL proposal #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
DSL proposal #72
Conversation
| } | ||
| ``` | ||
|
|
||
| Ingress/egress queues (illustrative bindings): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd imagine these rules are also part of the DSL itself, no?
(Personally, I'm a big fan of the idea that the order of declaration does not matter, and neither do file/dir names, so that we can just concatenate everything into a single "maroon source file" and "compile" it "in one piece". So that incress/egress definitions may well be in separate files, but still — they are part of the DSL imho.
|
|
||
| v1 state (current in the example above): | ||
|
|
||
| ```dsl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, now I see what the state is, it's for migration. Let's dig deeper into this on the next call!
Your idea may well be great, we need more realistic examples though. My thought was that there can only be two versions of the state, "current" and "next", or "emerging" and "previous" if you wish. And then migration is seamless "copy-on-write", almost like a constructor call during the cast.
But it's a longer conversation, and we may well think how deep down this rabbit hole we want to go as of now.
In practice this has more to do with the notion of "the lifetime of data objects" vs. the lifetime of "programming language objects". There exists higher-order primitives that have value as business logic entities. We probably want to annotate them as such somehow, since they are no longer just "records in the hash-map" or "pointers/references to an external maroon-powered storage" (such as S3 with checksums).
This would be a cool chat to have, and we almost have an example to ride with, so let's chat about it first thing next year?
docs/dsl-language.md
Outdated
|
|
||
| ## Fiber State and Persistence | ||
| - No global app state. Each fiber owns its own persistent state, defined inside that fiber. | ||
| - Define per‑fiber state in the DSL: within a `fiber` block, declare `state vN { ... }`. Only these schema types are persisted for that fiber. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, see comments above, we're getting close to this conversation — great!
docs/dsl-language.md
Outdated
| ## Fiber State and Persistence | ||
| - No global app state. Each fiber owns its own persistent state, defined inside that fiber. | ||
| - Define per‑fiber state in the DSL: within a `fiber` block, declare `state vN { ... }`. Only these schema types are persisted for that fiber. | ||
| - First activation initializes state deterministically. Upgrades use per‑fiber migrations: `migrate vN -> vN+1 { ... }`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually quite a bold statement that migration is per-fiber. That's one model I've considered first, it may well be the one we converge on, but I'm having second thoughts tbh.
Another option is to migrate per data type, i.e. the type (struct) of the next version is something the users can access only the new representation of starting at some era, and then after all "older" instances of that struct have disappeared, it can be obsoleted. I don't have a strong opinion yet, but this may well be a more powerful model.
| - Initialize state explicitly in `on start { ... }` or during `migrate` steps; avoid relying on implicit defaults. | ||
|
|
||
| ### Constructor parameters | ||
| - Fiber constructor parameters (identity, handles like queues, config) are read-only fields of the fiber instance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line is quite blurred for me now between on start and the constructor.
docs/dsl-language.md
Outdated
| - Fiber constructor parameters (identity, handles like queues, config) are read-only fields of the fiber instance. | ||
| - Access them as `self.<param>` inside the fiber (e.g., `self.name`, `self.inbox_queue`). | ||
| - Parameters cannot be reassigned; locals remain bare identifiers. Shadowing parameter names is not allowed. | ||
| - In `select`, the sugar `self.queue.await` is valid and desugars to `await recv(self.queue)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this for Rust compatibility or because we want it this way? Personally, I'm in favor of The One Way of Doing Things, so that we'd have a short list of language-level identifiers that we know exactly mean precise things (and they can be syntax-highlighted, etc.)
And await is one of them — personally I'd even say we can merge await and select, since await is just select with one arm.
Something like:
let e: Entry = await queue.next();
Vs.
let e: Entry1 | Entry2 = await { queue1.next(), queue2.next() };
Or, if the type is the same for queue1 and queue2:
let e: (Entry, idx) = await { (queue1.next(), 1), (queue2.next(), 2) };
This way it's the good old match afterwards, and each await arm (which is the select arm, technically), is an arbitrary async function — and if it's "in the middle of execution" whatever it has "polled" is logically destructed. And this also nicely solves the type system compilation time problem that some async events "can not be undone" — i.e. if we're talking about the queue where there's no way to "un-pop" the message, then the await / select arm for it must be of a single maroon step, otherwise it will break the "async borrow checking" invariant.
Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(We can still make the syntax Rust-friendly I believe, if only by just calling this construct Await! or maroon_await! instead of just await.
docs/dsl-language.md
Outdated
| - Fiber constructor parameters (identity, handles like queues, config) are read-only fields of the fiber instance. | ||
| - Access them as `self.<param>` inside the fiber (e.g., `self.name`, `self.inbox_queue`). | ||
| - Parameters cannot be reassigned; locals remain bare identifiers. Shadowing parameter names is not allowed. | ||
| - In `select`, the sugar `self.queue.await` is valid and desugars to `await recv(self.queue)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(We can still make the syntax Rust-friendly I believe, if only by just calling this construct Await! or maroon_await! instead of just await.
|
|
||
| ## Resource Limits | ||
| - We track CPU/memory/I/O “cost”. | ||
| - Per-transaction and per-fiber limits apply; hit a limit -> get a backpressure. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but perhaps not on all fibers, the priority here is to not have the "main business logic" get stuck somehow.
| ## Static Checks (examples) | ||
| - `pure` functions can’t call effects or timers. | ||
| - `Map`/`Set` keys must be orderable (canonical encoding available). | ||
| - `select` cases must be cancellable or time-bounded. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, unless they must be single-step by definition, for what can not be cancelled if it's already in flight!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed: remove this line.
| - Values: `Unit | Bool | I64 | U64 | Decimal{s} | Bytes | String | Vec<T> | Map<K,V> | Set<T> | struct | enum`. | ||
| - Queues: named FIFO channels of `Value`. | ||
| - Futures/Timers: one-shot futures; `after(ms)` creates a timer; `await` resolves. | ||
| - Fibers: define a fiber type with parameters (identity) and its private `state` and handlers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bad indent.
docs/dsl-language.md
Outdated
| - Futures/Timers: one-shot futures; `after(ms)` creates a timer; `await` resolves. | ||
| - Fibers: define a fiber type with parameters (identity) and its private `state` and handlers. | ||
|
|
||
| ### Queue API (explicit and sugar) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I seriously think we're better off not thinking about sugar and syntax desugaring yet — it's a v1, more important to have the skeleton right that to think of end-used cleanliness of the code imho.
| ### Select Syntax and Waitables | ||
| - Waitables: any `Future<T>` can be selected: `recv(queue)`, `after(ms)`, `external(...)`, etc. | ||
| - Canonical arms: | ||
| - `case await recv(queue) as v: T => { ... }` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually may be a great idea, to introduce case as!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed: rust compatible. In our case we won't have await because it will go to macros name
|
|
||
| ## Fiber State and Persistence | ||
| - No global app state. Each fiber owns its own persistent state, defined inside that fiber. | ||
| - Define per‑fiber state in the DSL: within a `fiber` block, declare `state current { ... }`. Optionally, during upgrades, also declare `state next { ... }`. Only these schema types are persisted for that fiber. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be discussed more. Firstly we should define schema migration for fiber interfaces(queues for cross-fibers and gateways for external APIs). How the data is stored inside fiber is important but not as much.
|
|
||
| ## Static Checks (examples) | ||
| - `pure` functions can’t call effects or timers. | ||
| - `Map`/`Set` keys must be orderable (canonical encoding available). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed: we should provide hashMaps as well but all the data structures should be deterministic (all operations)
No description provided.