I have been reading Revised-1 Report on the Kernel Programming Language 1 about Kernel, a variant Scheme defined by John Shutt.
I found it interesting read:
- I like his language ideas.
- I like his mechanisms for them.
- He scrupulously explains his motivations for each choice.
There is little else out there about Kernel. There's this paper, and an implementation in Scheme called SINK. Also a Wikipedia stub and some commentary on Lambda: The Ultimate. That's about it
FWIW, the name Kernel is very hard to search for. Googling "Kernel Scheme" generated mostly false hits. I'd suggest a change of name.
Everything is first-class / fexprs done right
Kernel's primary claim to goodness is that it makes everything a first-class object, even the things that Scheme wants to treat specially such as `define', `lambda', `and', etc. It does this by having its evaluator distinguish between "applicatives" which are essentially Scheme functions, and "operatives", which are somewhat like macros except2 that they take an explicit environment argument.
Kernel approaches continuations a little differently than Scheme. Unlike the rest of the paper, continuations were presented in a difficult way3, and it took me some careful reading to make sense of what he was saying.
Kernel has these primitives about continuations:
- call/cc The familiar Scheme construct
- extend-continuation. The purpose of this wasn't clear to me. It's not merely building a lambda form that will call the continuation. It seems to have to do with building trees of continuations, which guard-dynamic-extent treats specially.
- guard-dynamic-extent is a more powerful, safer form of dynamic-wind. Each guard knows a continuation and only intercepts children of that continuation.
- continuation->applicative which makes a continuation into essentially a function. Apparently in Kernel continuations are not simply applied but must be converted first.
It would have been nice to see some examples for how these constructs would be used.
These extensions seem intended largely for handling exceptions, but he doesn't specify an error format. (I like a fat error format myself, but that's a topic for another time)
Kernel has multiple-value bindings, and they are available everywhere, not just in a special construct (as in Common Lisp). So you can write something like:
(let (((a b c) (list 1 2 3))) (list c b a))
And you can bind not only lists but trees:
(let (((a (b c)) (list 1 (list 2 3)))) (list c b a))
I agree with this feature. I keep wanting this in Lisp and Scheme. I've built it with macros more than once, but it's nice to have it properly in the language.
But ISTM full pattern-matching is a fairly short jump away from this. As the writer of Emtest, I find pattern matching nearly indispensable.
Possible issues with full pattern-matching:
"What happens when a variable occurs twice in the pattern?"
The occurences must be eq?4 or the pattern fails to match.
"What if the pattern doesn't match?"
Nothing new. It's essentially the same situation as when a list is too long or too short.
"So how do you match objects that are not lists?"
Answer: Every constructor-like combiner5 should be able to have an associated pattern-matcher.
NB, for encapsulations created by make-encapsulation-type (8.1.1),
eshould not be considered constructor-like. If we were to try to use
ewith a symbol argument as a matcher, there would be no correct return.
- Always returning false would be wrong because the ctor might be instantiated with the same object, which should match.
Returning true would let the contents as returned by
dbind to the symbol, which would break encapsulation and incorrectly make the returns of different calls to
"If you have pattern-matchers, you need to indicate them,
presumably by symbols. How do you distinguish those symbols from
symbols to be bound?"
I suggest a quasiquote-style syntax, where low-quote indicates a non-literal sexp.
"What about unexpected problems?"
Having implemented more or less what I described for Emtest, I didn't find this especially problematic. However, I did not implement the quasiquote syntax because of how Elisp works.
"Is it really needed?"
Maybe not in the core, but then again, maybe. ISTM one should be able to define functions that have patterns as argument lists.
More to come
I will probably post about Kernel again when I have more thoroughly digested the paper.
[fn:4] (Footnote from nowhere, I just thought this was interesting) I was surprised to find that John has, among his weblinks, a link to my old conlang Allnoun. Apparently he is interested in conlanging. Maybe conlanging and programming language conlanging go together.
1 I suppose we should abbreviate it R-1RK. So should the next report be R0RK or just RK?
3 For instance, he refers to `$let/cc' and `apply-continuation' before he defines them, and the descriptions of extend-continuation and guard-dynamic-extent required two readings.
4 In Emtest's "match.el" I used `eql', but Kernel behaves differently than Elisp and has no `eql', so `eq?' is correct.
5 "Combiner" is Kernel's term for what you may think of as a function's actual workings. It differs from a function in that it does not cause its arguments to be evaluated. Applicatives (which you may think of as "normal functions") can be generated from combiners with "wrap".