4 ms·
This is good intuition. I tend to use this kind of idea in code these days. Here's an example: ($define! tree ($branch (dog ($branch (bark ($
by sparkie 11y ago
This is good intuition. I tend to use this kind of idea in code these days. Here's an example:
($define! tree ($branch
(dog ($branch
(bark ($branch
(loudly ($lambda () "WOOF!"))
(softly ($lambda () "woof"))))
(run ($branch
(fast ())
(slow ())))))
(cat ($branch
(meow ($lambda () "meow!"))
(purr ())
(run ())))))
($walk tree ($walk dog ($walk bark (loudly)))
=> "WOOF!"
If one attempted to just say (loudly), an error would be raised because loudly only exists in the context of bark, which only exists in the context of dog, etc. We walk through the tree to find this context, then run the function (loudly).
In the above Kernel code, the context is called an environment, and environments are first class. The evaluator takes an expression o and an environment e as arguments, and it is said that "o is evaluated in e".
The tree is basically described by construcing new environments, where for example, the symbol "dog" is bound to another environment containing the bindings "bark" and "run". I've used the word "$branch" in the example for simplicity, but this term already exists in Kernel under another name.
($define! $branch $bindings->environment)
$walk is implemented by combining the current environment with the one specified as its first operand, then the second operand is evaluated in the combined environment.
($define! $walk
($vau (env expr) dynenv
(eval expr (make-environment (eval env dynenv) dynenv))))
Programming this way can be pretty fun, as you're not restricted by some arbitrary "impressive sounding names" for accessing environments in restricted ways. You can do things the way you want, and it's fairly trivial to implement your own object systems.
I've termed it environment-oriented-programming. I basically use it as a means to implement OOP, generics, records, algebraic data types and whatnot. When combined with the use of other Kernel features like encapsulation types, it can be used to implement interesting type systems.
- javajosh 11y agoCool beans, sparkie. I'm working on something involving this shape (which is like a set of related call chains). The ability to call "loudly" by itself is a convenience only, a kind of syntactic sugar. I'm sure you could easily write the function that takes this tree, the string 'loudly' and emits '($walk tree ($walk dog ($walk bark (loudly)))'. :)
- sparkie 11y agoThere's certainly a few ways you could do that in Kernel, but I think doing this is less interesting than having to explicitly specify the environments to enter to find loudly to begin with. I see other potential problems with just specifying loudly alone which we'd look to avoid up-front, or explicitly handle as part of the tree. So for example if the cat were to become able to meow loudly, there would no longer be just one path into loudly, but we'd have two. Typically, we'd either make loudly a generic and specify (or infer) the type were referring to, or we'd have a dynamically-dispatched loudly which looks at say, the first argument and decides which one to execute. Both the these are trivial to implement as environments. However, the more interesting gain Kernel gives is that there is no way built into the language to enumerate the bindings in an environment (a deliberate omission). We can walk through the tree as given in this example (using car and cdr), but if there were some binding "(mouse the-mouse)" added to the tree, where the-mouse is already an environment, and not a list of instructions to build a new environment, then we cannot walk through its sub-tree, which may contain bindings such as "loudly". We can explicitly call ($binds? the-mouse loudly) to test this, iff "the-mouse" is an environment. This works by basically attempting to retreive the binding loudly and capturing the error thrown if it doesn't exist in the binding. In practice, we'd have a more interesting type for mouse, which contains an environment, but encapsulated (via make-encapsulation-type), with only predefined ways of accessing the bindings of the environment. With this setup, a binding in an environment behaves much like a capability. By knowing the name of a binding, you have the capability to use it. Proper use of the environment and encapsulation types gives us the flexibility to tightly constrain who can see "loudly".
- javajosh 11y ago>However, the more interesting gain Kernel gives is that there is no way built into the language to enumerate the bindings in an environment (a deliberate omission). Why not throw an error, or perhaps return a curry that takes an integer picking between multiple 'loudly' options? Its true that such 'do-special' would need global read access to the entire program, which is perhaps what you want to avoid?
- 11y ago