Passing closures to programs
15 August 2023One of the key features of Polaris is that it supports calling other
programs as a language construct rather than a function (like Bash, but
unlike, say, os.system
in Python). This sounds like it
wouldn’t make much of a difference beyond ergonomics, but it actually
gives the interpreter the freedom to do a few things that would be
impossible to do from inside the language. This is one of those!
Some CLI programs accept programs as callbacks that they will run at
some point. E.g. watch
, entr
. Using these in a
bash script means writing the callback to a separate file and passing
the name of that file — not too painful, but you’re already using bash
so the bar is pretty low.
Polaris on the other hand supports, you know, actual functions. So wouldn’t it be nice if we could just pass one of those to a program call? Imagine the possibilities if we could write programs like this
let counter = ref 0
!watch (\_ -> {
print(counter!)
counter := counter! + 1
0
})
How on earth are you going to implement this?
Great question! Here is where it gets fun.
Every time we pass a function to a program call, the interpreter actually creates a shell script in a new temporary directory and passes its path to the program call. This shell script then communicates with the main polaris process through named pipe files, sending stdin and receiving stdout. There, the polaris runtime creates a new async task that evaluates the closure with the correct stdin and stdout.
Importantly, this will not work with programs that somehow
modify the execution context such as chroot
or
env
. Maybe it should though? It might be possible to run
every IO operation in the child script.