some notes for tomorrow's work

This commit is contained in:
Scott Richmond 2025-06-26 23:28:17 -04:00
parent 888f5b62da
commit 00ebac17ce
3 changed files with 78 additions and 7 deletions

View File

@ -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

View File

@ -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.

View File

@ -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! ()