diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 7021bf1..79a5fcd 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1231,14 +1231,33 @@ fn yield! { () -> base :process (:yield) } +& TODO: implement these in the VM +fn alive? { + "Tells if the passed keyword is the id for a live process." + (pid as :keyword) -> base :process (:alive, pid) +} + +fn link! { + "Creates a 'hard link' between two processes: if either one dies, they both do." + (pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) + (pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2) + (pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2) +} + fn msgs () -> base :process (:msgs) +fn flush! () -> base :process (:flush) + +fn sleep! (ms as :number) -> base :process (:sleep, ms) + #{ self send msgs - spawn! + spawn! yield! + alive? + link! abs abs diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 0893af4..6872a04 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -827,3 +827,50 @@ Thus we can use it to communicate with the process. But everything else? Seems pretty straightforward. +#### Some time later... +I've implemented what I decribe above. It works! I'm low-key astonished. +It perfectly handles an infinitely recurring process! What the fuck. +Anyway, things left to do: +* [ ] `receive` forms are the big one: they require threading through the whole interpreter +* [ ] implement the missing process functions at the end of prelude +* [ ] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13 +* [ ] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum +* [ ] develop a design for how to deal with asynchronous io with js + +``` +fn agent/get (pid) -> { + send (pid, (:get, self ())) + receive { + (:response, value) -> value + } +} + +fn agent/store (pid, x) -> { + send (pid, (:store, x)) + :ok +} + +fn agent/update (pix, f) -> { + send (pid, (:update, f)) +} + +fn agent (state) -> receive { + (:get, pid) -> { + send (pid, (:response, state)) + agent (state) + } + (:update, pid, f) -> { + agent (f (state)) + } + (:store, pid, x) -> { + agent (x) + } +} +``` + +Two things that pop out to me: +* The way this works is just to yield immediately. This actually makes a lot of sense. If we put them next to one another, there's no risk that there'll be backlogged `(:response, x)` messages in the mbx, right? But that makes me a little queasy. +* The way `gen_server` works is pretty deep inversion of control; you effectively write callbacks for the `gen_server` to call. I'm not sure that's how we want to do things in Ludus; it's a handy pattern, and easy. But it's not simple. But also worth investigating. In any event, it's the foundation of all the other process patterns Elixir has developed. I need an intuiation around it. + + + diff --git a/sandbox.ld b/sandbox.ld index 6c454cc..8e6f6cb 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1,10 +1,15 @@ -let pid = spawn! (fn () -> { - print! (self ()) - print! (msgs ()) -}) +fn simple_reporter () -> { + print! (self (), msgs ()) +} -send (pid, :foo) -send (pid, :bar) +fn hanger () -> hanger () + +let foo = spawn! (hanger) +let bar = spawn! (simple_reporter) +let baz = spawn! (fn () -> panic! :oops) + +send (foo, [:foo, :bar, :baz]) +send (bar, (1, 2, 3)) yield! ()