Passing closures to programs

15 August 2023

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