4 ms·
Typically you want to talk about a function like eval :: Syntax -> Semantics In Scheme `Syntax` is some kind of tree-like Sexpr thing or possibly a more w
by tel 12y ago
Typically you want to talk about a function like
eval :: Syntax -> Semantics
In Scheme `Syntax` is some kind of tree-like Sexpr thing or possibly a more well-defined AST. So let's focus on `Semantics` instead.
Ultimately, a perfectly fine semantics is `IO ()`. As Haskell's "sin bin", `IO` circumscribes all of the effects you need. So, the entirety of this game is to capture as much effect as you can "purely" so that it is reflected intelligently in the type.
The key pure semantic component for managing changing state is to focus on "changes" instead of points in state space. This we need a notion of the initial state (the empty env) and then a series of functions
Env -> Env
If we let `Semantics = Env -> Env` then we've already captured a meaningful chunk of Scheme semantics purely. We cannot observe Scheme side effects---they would have to be faked and thrown away---but we can observe how the env evolves. To be a bit more clear, this eval "respects" the syntax properly---if `seq :: Syntax -> Syntax -> Syntax` sequences two Sexprs then
eval (seq sexpr1 sexpr2) = eval sexpr2 . eval sexpr1
e.g. sequencing of syntax is just function composition in the "Env transformer semantics".
So that's how we avoid IORefs.
From here we just build more. Another fun thing to add would be input and output. Here we note that any given Syntactic fragment no longer merely transforms Envs but may also either attempt to print out some text or want to read it in.
data Print e = Simple e | Read (Text -> e) | Write (Text, e)
type Semantics = Env -> Print Env
and now we'd expect things like
eval "(read)" env === Read (\input -> env)
eval "(write \"foo\") env === Write ("foo", env)