Posted on Tue Apr 30, 2019
I have ported some old Scheme code under CHICKEN. The result is a small set of packages: MMCK Checks; MMCK Fectors; MMCK PFDS, which is still under development. It was somewhat fun; I still enjoy reorganising code.
I’m making little progress understanding CHICKEN’s programming features and ecosystem. Native executables and shared libraries are good, but I notice that they are somewhat bigger in file size that I thought; not really a problem (being that I am used to the huge files of Vicare), but still I wonder what’s in there to make binary code so big.
I’m starting to get the hang of exceptional–condition handling and exceptional–condition objects;
I can confirm my first impression: I do not like it. The handlers and condition objects defined by
r6rs are better in my humble opinion; and maybe I’m not the only
one. In the small amount of CHICKEN code I have seen so far, written by people with much more
experience than myself, there is very little design of exceptional–condition object–types; mostly,
there is just a raw error call in which the exception description is the message string: not
an object we can use programmatically to react differently to different error kinds.
So far, the one CHICKEN egg I cannot live without is COOPS: a library implementing CLOS–like language extensions. It is incomplete, more metaobject protocol must be implemented. Its documentation is ugly and incomplete. It is based upon ScmObj by Dorai Sitaram, a code base I know because I, too, have used it as base to implement extensions for Vicare.
In the past, when approaching new Scheme implementations, I built a library of language extensions to adapt the programming environment to my needs; I will try the same with CHICKEN and see what will happen.
Lately I’ve been thinking about how a statically typed Scheme language could be implemented to suit my taste. Would it still be a “Scheme” language, or would it be a non–Scheme dialect of Lisp? Maybe the latter.
Let’s consider the function string->number as defined by r6rs:
(string->number "123") ⇒ 123 (string->number "ciao") ⇒ #f
this behaviour allows us to write a typical Scheme idiom:
(or (string->number arg)
(raise-an-error))
A type signature for this function would be something like:
(<string>) -> (or <number> <false>)
no exception is raised if the argument is not a number. I do not like this complex type signature: I want a single, known, type for each argument and each return value. But then the typical Scheme idiom above would not be possible.
How would one implement string->number then? The simpler way would be to raise an exception:
(try
(let* ((arg (do-this-thing))
(num (string->number arg)))
(do-that-thing))
(catch E
((&string-does-not-represent-a-number)
(react-this-way))
(else
(react-that-way))))
nowadays every programmer can understand this code, even if he/she is not a Scheme adept. A problem
with raising exceptions is that it is hard to distinguish the form that raised the exception: which
chunk of code inside the body raised &string-does-not-represent-a-number?
A more general design is to hand an escape function as second argument: if the input string cannot
be converted to a string, string->number calls the escape function, let’s say with no
arguments:
(with-escape-handlers
(let* ((arg (do-this-thing))
(num (string->number arg escape-string-conversion)))
(do-that-thing))
((escape-string-conversion)
(react-this-way)))
we can imagine the syntax with-escape-handlers to expand into something like (untested):
(call/cc
(lambda (return)
(call/cc
(lambda (escape-string-conversion)
(let* ((arg (do-this-thing))
(num (string->number arg escape-string-conversion)))
(return (do-that-thing)))))
(react-this-way)))
not easy to understand if you are not into Scheme; somewhat messy, when multiple functions can call
multiple escape functions; very general because each form inside the body of
with-escape-handlers can cause the execution flow to branch into a different execution
path, but still multiple forms can call the same escape function.
An even more general design is to hand a continuation function as second argument:
(let* ((arg (do-this-thing))
(num (string->number arg (lambda (exception-object)
(react-this-way)))))
(do-that-thing))
and string->number should tail–call its continuation if a conversion error occurs.
Weird…
Enough for today.