Kay is a dynamic and concatenative language that is heavily inspired by Joy and XY. Parts of the implementation are based on Thun which is also a Joy implementation.
It is named after Kathleen Rita Antonelli. She was one of the six original programmers of the ENIAC and known as Kay McNulty, an Irish-born American computer programmer. It is also a firm nod to Alan Kay who - even though I never had to fortune to meet him - changed my professional life.
Kay is just a toy (for now). Unless you are absolutely crazy you really should not use this for any kind of serious stuff. It is meant like a vehicle to learn about and experiment with some of the ideas that Manfred von Thun was having fun with when he designed Joy.
Kay is capable of some pretty weird things. Let's show off the most useless program ever. This doesn't calculate anything useful but it shows off some of her symbolic computation capabilities and the crazy ways you can manipulate the interpreter.
If you feel like this introduction is going way too fast then do not worry. It is just meant to give you a glimpse of what Kay can do, a proper tutorial and manual are in the works.
We'll try to define the symbol 2 * (3 + 4)
just because we are mad. Note that this name has spaces in it so its extremely unlikely the following will work:
kay> 2 * (3 + 4) == 4 3 + 2 *.
line 1:12 mismatched input '==' expecting '.'
Runtime exception: two arguments needed for `*`
Indeed she does not like this formulation.
I'm actually a bit surprised this gives a runtime and a parser error. This needs some looking into.
No sane programming language would accept this though so it is a good thing Kay does not either.
However, we are not defeated yet. Since we have the following:
intern
which converts a string to a symbol- quotations which are basically lists that act like a program
def
which takes two items from the stack and converts them into a user-defined definition.
Lets force our way:
kay> "2 * (3 + 4)" intern. // 1
2 * (3 + 4) <- top
At this point we managed to push our weird symbol name onto the stack as a symbol. We know it's a symbol since it really cannot be anything else by the way it is printed. However, we can check if we want to know for sure. The name
operation converts the value on top of the stack into a string representation. For symbols, this would return the name of the symbol as a string.
So with the symbol 2 * (3 + 4)
on top of the stack we expect this to be turned into the string "2 * (3 + 4)"
. Let's see what happens:
kay> name.
"2 * (3 + 4)" <- top
Indeed we get back the string that represents our symbol name. We can easily turn it back into a symbol again by evaluating another intern
operation.
kay> intern.
2 * (3 + 4) <- top
So now that we are sure we can use this insane symbol name we can push the body:
kay> [4 3 + 2 *]. // 2
[4 3 + 2 *] <- top
2 * (3 + 4)
We now have two things on the stack, our symbol name 2 * (3 + 4)
and a quotation that represents the body for that symbol. Is the body even correct? Does it correctly evaluate the expression 2 * (3 + 4)
? This should evaluate to 14
but we haven't really bothered to check. Let's do that right now using the x
combinator. This will evaluate the program on top op the stack without removing it.
kay> x.
14 <- top
[4 3 + 2 *]
2 * (3 + 4)
It correctly evaluates to 14
so it seems the body for our defintion is fine. However we now have this 14
sitting on top of the stack which we need to remove otherwise we cannot invoke the def
operation which expects a quotation on top and a symbol below.
But since we are here to learn we just go ahead and issue a def
anyway. Just to see what happens:
kay> def.
Runtime exception: quotation as first argument needed for `def`
Yep that was more or less what was to be expected. Luckily the stack did not get too mutilated but we still have that useless 14
sitting on top.
kay> .
14 <- top
[4 3 + 2 *]
2 * (3 + 4)
We can get rid of it by evaluating a pop
operation:
kay> pop.
[4 3 + 2 *] <- top
2 * (3 + 4)
And now we are ready to issue the infamous def
which will bind the symbol 2 * (3 + 4)
with the body [4 3 + 2 *]
in the interpreter environment:
kay> def. // 3
Sadly we do not get any great song or fanfare from def
when it succeeds. It just silently does its job and only gives feedback when things go wrong.
- We use
intern
to turn the string on top of the stack into a symbol. - We push the body (the definition) for this symbol onto the stack as a quotation.
- We evaluate
def
in order to take the symbol and definition and add a new user-defined entry to the interpreter environment.
Did this actually work? Well, we can check:
kay> "2 * (3 + 4)" intern body.
[4 3 + 2 *] <- top
Evaluating body
will push the definition of the symbol 2 * (3 + 4)
onto the stack. And we do get back the quotation we pushed earlier so this seems to work. Now how to call it?
kay> "2 * (3 + 4)" intern. // 1
2 * (3 + 4) <- top
kay> body. // 2
[4 3 + 2 *] <- top
kay> i. // 3
14 <- top
So what is happening here?
- We just use
intern
to make our symbol and push it onto the stack. - We use
body
to push the definition of that symbol onto the stack. - We use
i
to evaluate the quotation that is on top of the stack.
If we execute this program using trace
we can get some more insight:
kay> ["2 * (3 + 4)" intern body i] trace.
===========================================
stack : queue
-------------------------------------------
: "2 * (3 + 4)" intern body i
"2 * (3 + 4)" : intern body i
2 * (3 + 4) : body i
[4 3 + 2 *] : i
: 4 3 + 2 *
4 : 3 + 2 *
4 3 : + 2 *
7 : 2 *
7 2 : *
14 :
-------------------------------------------
14 <- top
The trace
combinator takes a quotation on top of the stack and executes it like a program while recording the stack and queue states for every step. At the end it will print a formatted trace of each step before the contents of the stack are printed.
Invoking body
seems a bit of a cheat. Can we invoke the symbol directly? We could do the following:
kay> kay> ["2 * (3 + 4)" intern unit i] trace.
===========================================
stack : queue
-------------------------------------------
: "2 * (3 + 4)" intern unit i
"2 * (3 + 4)" : intern unit i
2 * (3 + 4) : unit i
[2 * (3 + 4)] : i
: 2 * (3 + 4)
: 4 3 + 2 *
4 : 3 + 2 *
4 3 : + 2 *
7 : 2 *
7 2 : *
14 :
-------------------------------------------
14 <- top
Here we use unit
to convert the symbol into a quotation which we then evaluate using the i
combinator. But how much of an improvement is this?
If we evaluate the trace we can see that even though unit
is theoretically a bit cleaner, in practice using body
requires less instructions to be queued. This is caused by body
directly putting the term associated with symbol 2 * (3 + 4)
on the stack while the unit
construction causes the term associated with the symbol 2 * (3 + 4)
to be prepended onto the queue.
Coming soon!
Dynamic definitions is a feature that Joy does not have. In Joy you can have static definitions which you can duplicate and change if you want but you cannot undefine and redefine definitions dynamically - they are baked in at read time. In Kay, built-in definitions cannot be changed but all user-definitions can be defined and undefined at runtime using def
and undef
with operands on the stack like any other operation.
Note that you currently cannot redefine a symbol. You will first have to explicitly undefine it before you can define it again. There might be a
redef
operation in the future.
A traditional Joy definition looks like symbol == ...
and these are handled differently by the interpreter in the sense that they are not evaluated. The interpreter will treat the dots ...
like a list of factors (i.e. a term) and associate symbol
with this term in the interpreter environment. These definitions are baked in at read time on what amounts to parser level.
In other words, if you do something like this:
kay> A == 2 3 +.
What really happens is that the quotation [2 3 +]
is associated with symbol A
in the interpreter environment. This bypasses all normal evaluation rules.
In Kay, there is another (dynamic) way to create a definition using the normal evaluation rules and a new combinator called def
(which combines with undef
to undefine symbols).
The static A == 2 3 +
definition from above is equivalent to the following dynamic definition:
kay> "A" intern [2 3 +] def.
kay> A.
5 <- top
Note that even though this definition is dynamic, The def
operation will not evaluate the quotation [2 3 +]
. It will just accept this quoted and associate it with the symbol that was just below it on the stack. The definition will only be evaluated when it it actually used in the queue.
We can evaluate the behavior using trace
as is shown in the following example:
kay> [A] trace.
===========
stack : queue
-----------
: A
: 2 3 +
2 : 3 +
2 3 : +
5 :
-----------
5 <- top
The symbol A
is unrolled at the last moment when it was encountered by the interpreter and it had no other choice but to look it up in its environment.
At the moment it's not quite clear how useful this is but it at least allows you to do funky things:
kay> ["A" intern [3 2 +] def A A +] trace.
========================================
stack : queue
----------------------------------------
: "A" intern [3 2 +] def A A +
"A" : intern [3 2 +] def A A +
A : [3 2 +] def A A +
A [3 2 +] : def A A +
: A A +
: 3 2 + A +
3 : 2 + A +
3 2 : + A +
5 : A +
5 : 3 2 + +
5 3 : 2 + +
5 3 2 : + +
5 5 : +
10 :
----------------------------------------
This creates a program that defines A
and uses it to perform some computations.
If at some point we want to undefine the symbol A
we can use the undef
operation. This will take a string or a symbol on top of the stack. Note that you cannot redefine (overwrite) an existing definition. You will have to use undef
first.
kay> A == 3 2 +.
kay> "A" intern user.
true <- top
kay> "A" intern [4 5 *] def.
Runtime exception: `A' is already defined as: 3 2 +
kay> pop.
kay> "A" undef.
kay> "A" intern user.
false <- top
Below are just some reference samples showing off some basic symbolics you can do with Kay.
kay> S == dup *
.
kay> A == S.
kay> X == A.
kay> [2 X] trace.
===========
stack : queue
-----------
: 2 X
2 : X
2 : A
2 : S
2 : dup *
2 : 2 *
2 2 : *
4 :
-----------
4 <- top