Dec 16, 2008 12:36
An idea for a nasty little hack of Joy which may or may not be interesting.
A thing that annoys me a lot about both Joy and Lisp - in fact most computer languages - is that, having descended from mathematical notation, they both inherit mathematic's fixation with *numbers*. Text is a second-class citizen, to the extent that it's considered natural that a sequence of digits should always represent itself but a sequence of letters should always be a symbol that stands in for something else.
This makes sense in the world of maths, especially algebra, where the 'real' objects we deal with *are* numbers, and words are always just *names* for a quantity that is 'really' a number.
But this does not make sense in the world of text processing, where a word is in fact a word and not a variable name. You do NOT want to automatically evaluate every word that comes down the line if it is in fact just a word.
(Leaving aside for now the issue of just 'where' and how you look up the value of a symbol).
A counterpart of this odd quirk is that numbers cannot normally be the *name* of something. But that's also quite weird, because a number can easily enough be an *argument* to a function, and what is name resolution or lookup but passing an argument to a function?
Most languages get around this by treating numbers as self-evaluating, text strings as names to be looked up and/or executed, and quoting or escaping text somehow to prevent it being automatically evaluated. This works, but gives special semantics to the 'quote' form. In Lisp this is submerged in the wider case that quote is just a macro (actually two sorts of macro, a normal macro for (quote) and a reader macro for ', but never mind). We still end up with one semantics for special forms and another for functions.
Now with Joy, we have the interesting case that even self-evaluating numbers are still just *programs* that happen to add themselves to the stack. But words are again, like Lisp, treated specially - looked up in the global dictionary, then executed. This is awkward.
Here's the proposal:
1. Numbers act as they always do, they add themselves to the stacks.
2. Quotations act the same, they add themselves to the stack.
3. Words - which in Joy would normally be executed as the names of combinators - are like numbers, just added to the stack.
4. We add a set of punctuation marks to handle lookup and execution. I suggest three to start with:
' - lookup the topmost stack element in the current dictionary (defined I'm not yet sure how), and dump the result to the stack without executing it. This means we can have both numbers and quotations as names, which could be important.
. - execute the topmost stack element. If it's a literal, treat it as the name of a Joy builtin combinator and execute it. If it's a quotation, evaluate it (using the same rules here) in a similar fashion to Joy's 'i' combinator
! - do both: lookup the topmost stack element in the current dictionary, then execute the topmost result. The assumption being that it will be the name of a combinator. ! is identical to doing '. in sequence.
Combinators written like this will look a little excitable, but this could be interesting.
[x' zork frob! map. reduce.] - user-defined combinator
[x' y' z']. - evaluate a quotation that happens to just substitute variables
At first glance it looks as if this might give us a similar syntactic effect to Common Lisp's separation of symbols and functions into two namespaces. It also gives us a separate third 'namespace', for system as opposed to user-defined combinators, which might or might be a good idea. A possible benefit is that it would be easy to scan a quotation to see whether it does any evaluation or just does variable expansion. Variable expansion (lookup) would be defined as always consuming no stack and always adding one element: a literal, a quotation, or an 'unknown/error' symbol.
A particularly evil low-level implementation might define raw 'evaluate' as only working on quotations or *numbers*, with the number being a memory address or some other low-level representation. Attempting to raw-evaluate a word would be an error. Then 'builtins' like 'map' and 'reduce' would be names mapping to the real builtins, and all combinators would end with !
ie: [x' zork frob! map! reduce!]
There are various ways we could tweak this, but this is to try to make clear what is actually happening at a low level.
' itself is probably defined as [DICTIONARY LOOKUP] where DICTIONARY drops the currently active dictionary onto the stack and LOOKUP is a builtin for searching a dictionary.
Adding dictionaries to Joy in this way of course does all sorts of horrible damage to its pure stack properties. But all practical implementations of it do such damage anyway on an ad-hoc basis, this is just attempting to standardise it.
The question then is how and if we can sensibly reason about nested dictionaries in a world of combinators. Do we have perhaps a second 'dictionary stack'? Or a 'meta-dictionary'? Can ordinary combinators manipulate the dictionary structure or do we need special punctuation? And what would be the most Joy-like way of doing that?
For a start, this seems like it would make reasoning about what a combinator does very nasty indeed. If the only real builtins are ' and . then suddenly we have no guarantees at all about the meaning of a combinator, as its effect will depend entirely on both the incoming stack state and the dictionary state, neither of which we can be sure of. Even if we have some form of lexical scoping, the meaning of all words becomes entirely runtime context-sensitive. Ouch.
(Okay, we can make *some* guarantees. If we look at a quotation and see a word followed immediately by a ., we know it's a builtin. If we see a word and a '. and we can make guarantees about the state of the dictionary, same. We only have problems if we see more . or ' s than constant words provided, or we see [quotation] then ' or . We're not really in too much different a state than ordinary Joy if we saw an 'i'. It's really just the dictionary that throws us.)