I started talking about explicit environments on #37 and on Slack. Let me be both broader and deeper in an issue of its own.
The idea is to introduce a crude but alarmingly effective form of what some might call "object orientation" if they wanted to try to wind us up: as a tactic, it wouldn't work on me, anyway.
Yer regular "mathematical" functions are, at least in a cbv language, an extreme form of contextualization. In f(a)
, f
contextualizes a
by waiting patiently for its value, and then postprocessing it. Meanwhile, back in dear old Pascal, one could write
where r
is an expression of record type and c
is a command for which r
's fields are in scope. c
could, of course, be return e
, but it was a tad annoying that there was no version of with
in the expression language, only in the command language. One of my main irritations (for it is superficial) with oo style, is all that projection all over the place like bad acne. I went through a period (ha ha) of pronouncing .
as "spot" rather than "dot". IIRC, there's a version of this rant in my thesis. Anyhow, r
contextualises c
by allowing lookup as c
runs, and quietly going away when c
stops. It's pretty much the other extreme from functions, when it comes to what activities "contextualization" amounts to.
So I'm proposing an notion of first class environment e
which allows us to write
to contextualize the evaluation of a
by the value bindings in e
.
What is such an e
? It's an association tree.
- The empty environment is
[]
- A singleton environment is
['var | value]
- A binary environment is
[env0 | env1]
where env0
's bindings may shadow env1
's.
So, we're exploiting the fact that atoms apart from []
are the quotations of identifiers. (Indeed, perhaps it is now stretching a point to call []
an atom, given that it does not quote an identifier, you can't write it with a quote-mark, etc. But it is, at least, genuinely indivisible, which quoted identifiers wouldn't be if were to allow them to explode as strings.) We can tell the difference between a singleton and a binary by checking their heads. We can thus crunch such a structure down to a Haskell map or a JS object, when we need to use it as an actual environment.
Why is it a tree and not a list? It's a monoid, so why bother normalising it? Also, you get to preserve sharing when you build bigger environments from smaller ones. Bargain basement inheritance is gained by the expedient of shoving old environments at the back end of new ones.
In the it-looks-like-C-but-it-so-isn't brutality of this language, I am sorely sorely tempted to make the notation
sugar for
so that we can (never let that mean assignment and also) write things like
and yer common or garden
let x = s in t
becomes
(x = s)(t)
Around the corner, there's the option to consider pattern matching as "compute an explicit environment or abort", where our beloved ->
is also concealing yet another use of application-as-contextualization.
It's cheap; it's fun; it's encapsulation for hooligans. It's the polite version of dynamic binding, just as we're already doing the polite version of delimited control.
Shall we do this?