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.