Skip to content

Conversation

@akantsevoi
Copy link
Collaborator

No description provided.

}
```

Ingress/egress queues (illustrative bindings):
Copy link
Contributor

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
Copy link
Contributor

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?


## 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.
Copy link
Contributor

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!

## 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 { ... }`.
Copy link
Contributor

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.
Copy link
Contributor

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.

- 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)`.
Copy link
Contributor

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?

Copy link
Contributor

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.

- 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)`.
Copy link
Contributor

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.
Copy link
Contributor

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.
Copy link
Contributor

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!

Copy link
Collaborator Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad indent.

- 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)
Copy link
Contributor

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 => { ... }`
Copy link
Contributor

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!

Copy link
Collaborator Author

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.
Copy link
Collaborator Author

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).
Copy link
Collaborator Author

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants