If I may once again request the community's wisdom on a small matter...
I am designing a Lisp dialect (and interpreter, which is largely coded
by now) for my pet operating system, and wish to add an FFI. I've heard
of every implementation of CL having its own, in addition to
compatibility layers such as the Universal Foreign Function Interface
and Common Foreign Function Interface, but which is best? By "best", I
mean 1) Easy for systems hackers to use and 2) Powerful and capable.
The goal is to create a Lisp interpreter which could be booted into and
used as a systems progamming language, ie: to write important libraries
and device drivers in. I can code the primitives of the FFI into the
interpreter (once I finally get the abominable returning tagbody error
sorted out), but I want to make sure it'll have enough power to code the
entire system (besides the kernel itself) in Lisp.
Also, would it be better to export Lisp functions in binary form
(callable from C), or simply provide the one exported function eval(),
which would be take a C string containing the Lisp expression to evaluate?
And yes, this interpreter will be released to the public so you can tell
me how bad my code is.
Todah rabah,
Eli
--
The science of economics is the cleverest proof of free will yet
constructed.
From: Friedrich Dominicus
Subject: Re: Foreign Function Interfaces
Date:
Message-ID: <87lktr5y3q.fsf@flarge.here>
Eli Gottlieb <···········@gmail.com> writes:
> If I may once again request the community's wisdom on a small matter...
>
> I am designing a Lisp dialect (and interpreter, which is largely coded
> by now) for my pet operating system, and wish to add an FFI. I've
> heard of every implementation of CL having its own, in addition to
> compatibility layers such as the Universal Foreign Function Interface
> and Common Foreign Function Interface, but which is best? By "best",
> I mean 1) Easy for systems hackers to use and 2) Powerful and
>capable.
Well it seems that you have gone your way, however I'm curious why
you had to implement you own Lisp.
I guess you have seen
Lush: http://lush.sourceforge.net/
ECL :
diverse Schemes etc.
> The goal is to create a Lisp interpreter which could be booted into
> and used as a systems progamming language, ie: to write important
> libraries and device drivers in.
Hm according to their docs Lush was developed for that purpose. Maybe
you should check their FLI?
Regards
Friedrich
--
Please remove just-for-news- to reply via e-mail.
Friedrich Dominicus wrote:
> Eli Gottlieb <···········@gmail.com> writes:
>
>
>>If I may once again request the community's wisdom on a small matter...
>>
>>I am designing a Lisp dialect (and interpreter, which is largely coded
>>by now) for my pet operating system, and wish to add an FFI. I've
>>heard of every implementation of CL having its own, in addition to
>>compatibility layers such as the Universal Foreign Function Interface
>>and Common Foreign Function Interface, but which is best? By "best",
>>I mean 1) Easy for systems hackers to use and 2) Powerful and
>>capable.
>
> Well it seems that you have gone your way, however I'm curious why
> you had to implement you own Lisp.
I wanted language constructs not available in current Lisps. Examples:
Environment objects, all-code-is-a-function, and dynamic coding capability.
>
> I guess you have seen
> Lush: http://lush.sourceforge.net/
> ECL :
>
> diverse Schemes etc.
Yep. Looked at all of those (including several of the "diverse
Schemes") before deciding to write my own.
ECLS was extremely interesting, given that its functions are "naturally"
compiled with C-like protocols. I corresponded Juan Jose Garcia Ripoll
about it, actually. Apparently documentation on adding and removing
primitive functions is scarce.
>
>
>>The goal is to create a Lisp interpreter which could be booted into
>>and used as a systems progamming language, ie: to write important
>>libraries and device drivers in.
>
>
> Hm according to their docs Lush was developed for that purpose. Maybe
> you should check their FLI?
>
> Regards
> Friedrich
"Lush is an object-oriented programming language designed for
researchers, experimenters, and engineers interested in large-scale
numerical and graphic applications."
I considered Lush, then realized it seems to want every library under
the sun in order to work properly. If it can be compiled down, though,
it'll work. It just has to compile down so far that even POSIX support
isn't even needed (for device drivers).
Loving it when a Lisper reacts viscerally with "don't break the herd!"
rather than answering the asked question,
Eli Gottlieb (NOT planning world domination of my Lisp)
--
The science of economics is the cleverest proof of free will yet
constructed.
I would suggest basing your FFI around CFFI, as that appears to be the
current best portable FFI.
Putting on my device driver programmer hat for a moment, I would expect
that one truely useful feature of a Lisp that is designed for embedded
work would be the ability to easily specify the types of variables
being written to the hardware.
For example, in C.
typedef unsigned int uint32;
uint32 poke (uint32 *addr, uint32 value)
{
*addr = value;
return *addr;
}
I think that a Lisp aimed at device drivers would need to be able to
explicitly handle static types easily, the syntax could be like:
(define-typed-func :uint32 poke ((:uint32 addr) (:uint32 value))
(setf (mem-ref addr) value)
(mem-ref addr))
The Lisp is a little more verbose, but I think device drivers is a
domain that C excels in because it maps so well to the underlying
hardware. If I were doing this, I would think about making [] the
defererence operator, so that
(mem-ref addr) -> [addr]
(setf (mem-ref addr) x) -> (setf [addr] x)
(mem-ref (+ addr 5)) === (mem-ref addr 5) -> [addr 5]
You mention that your Lisp will be interpreted, is compilation an
eventual goal?
Good luck with your Lisp!
Brad
bradb wrote:
> I would suggest basing your FFI around CFFI, as that appears to be the
> current best portable FFI.
Thanks.
> Putting on my device driver programmer hat for a moment, I would expect
> that one truely useful feature of a Lisp that is designed for embedded
> work would be the ability to easily specify the types of variables
> being written to the hardware.
> For example, in C.
>
> typedef unsigned int uint32;
> uint32 poke (uint32 *addr, uint32 value)
> {
> *addr = value;
> return *addr;
> }
>
> I think that a Lisp aimed at device drivers would need to be able to
> explicitly handle static types easily, the syntax could be like:
>
> (define-typed-func :uint32 poke ((:uint32 addr) (:uint32 value))
> (setf (mem-ref addr) value)
> (mem-ref addr))
>
> The Lisp is a little more verbose, but I think device drivers is a
> domain that C excels in because it maps so well to the underlying
> hardware. If I were doing this, I would think about making [] the
> defererence operator, so that
> (mem-ref addr) -> [addr]
> (setf (mem-ref addr) x) -> (setf [addr] x)
> (mem-ref (+ addr 5)) === (mem-ref addr 5) -> [addr 5]
Makes quite a bit of sense. The Lisp code would need to be able to know
about the hardware representation of low-level data.
>
> You mention that your Lisp will be interpreted, is compilation an
> eventual goal?
When I learn about compilation.
--
The science of economics is the cleverest proof of free will yet
constructed.
Eli Gottlieb <···········@gmail.com> writes:
> bradb wrote:
>> You mention that your Lisp will be interpreted, is compilation an
>> eventual goal?
>
> When I learn about compilation.
Whatever you do, think about compilation before you design your data
structures. Because of how I handled symbol lookups in a lisp
interpreter I wrote a few years ago, it is impossible to actually gain
anything by writing a compiler. I didn't understand CL symbols so I
didn't copy them, and they are a Good Idea (TM) if you're going to be
compiling things.
Ari Johnson wrote:
> Eli Gottlieb <···········@gmail.com> writes:
>
>
>>bradb wrote:
>>
>>>You mention that your Lisp will be interpreted, is compilation an
>>>eventual goal?
>>
>>When I learn about compilation.
>
>
> Whatever you do, think about compilation before you design your data
> structures. Because of how I handled symbol lookups in a lisp
> interpreter I wrote a few years ago, it is impossible to actually gain
> anything by writing a compiler. I didn't understand CL symbols so I
> didn't copy them, and they are a Good Idea (TM) if you're going to be
> compiling things.
"Didn't copy them"?
I just wrote a descendant of TLispObject (which makes it Pascal
type-safe with other Lisp data and subjects it to reference counting)
which contained a symbol name string. A function obtains symbols by
name, and symbol equality is determined by performing an object
comparison. Will that work?
--
The science of economics is the cleverest proof of free will yet
constructed.
Eli Gottlieb <···········@gmail.com> writes:
> Ari Johnson wrote:
>> Whatever you do, think about compilation before you design your data
>> structures. Because of how I handled symbol lookups in a lisp
>> interpreter I wrote a few years ago, it is impossible to actually gain
>> anything by writing a compiler. I didn't understand CL symbols so I
>> didn't copy them, and they are a Good Idea (TM) if you're going to be
>> compiling things.
> "Didn't copy them"?
>
> I just wrote a descendant of TLispObject (which makes it Pascal
> type-safe with other Lisp data and subjects it to reference counting)
> which contained a symbol name string. A function obtains symbols by
> name, and symbol equality is determined by performing an object
> comparison. Will that work?
Here's what I did "wrong" ...
I wanted to have one lisp image of sorts that provided separation of
environment for many users. If you're interested, I wrote it as a
control language for a space simulation engine that I created for MUSH
games to use. I wanted to allow the players of the game to use this
lisp dialect to control their ships and so forth. It's actually very
successful for that, even if nobody but myself has coded for it (it's
so much easier to code an autopilot in lisp than in C or, worse,
MUSHcode). So each ship has to have its own environment, but I didn't
want one lisp image per ship since it seemed wasteful of memory.
So what I have is a tree structure of "environments." Each one is a
4-element vector of the form #(symbols global dynamic parent), where
symbols is an alist of symbols and values, global is a pointer to the
global environment (the procedures and such that the language provides
are in that environment's symbol list), dynamic is a pointer to the
dynamic environment, which is defined as the local root-level one (so,
global symbols for the current user), and parent is a pointer to the
parent environment.
Environments are created when entering a funcall and when entering a
let body.
The problem with this in terms of compilation is that, unless I make
it extremely smart (which I'm frankly not willing to spend the time
doing because I'm not a compiler wizard to begin with and this is just
a hobby project), the compiler will not be able to do any better than
compiling to bytecode and placing the symbol name into the output,
while the bytecode interpreter would have to read the symbol name and
then look it up through the alists of the environment chain to
determine the symbol value.
Also, I do not have separate namespaces for variables and functions.
What I would prefer to have done (and it's possible to change to this,
but involves extensive changes that I don't really have time for, see
above) is to have at least a value slot on the symbol and then stored
just a list of symbols rather than an alist of (symbol . value) pairs.
Essentially, my symbols only have a name and nothing else. (In fact,
other than parsing, printing, and evaluating, they are the exact same
underlying C code as for strings.)
Contrast with Common Lisp, where symbols have the following slots:
Name
Package
Value
Function
Property list
...possibly more that I'm forgetting in this quick post.
Trying to figure out how to implement a compiler for my lisp dialect
was one of the stepping stones to grokking CL symbols.
Ari Johnson wrote:
> Eli Gottlieb <···········@gmail.com> writes:
>
>
>>Ari Johnson wrote:
>>
>>>Whatever you do, think about compilation before you design your data
>>>structures. Because of how I handled symbol lookups in a lisp
>>>interpreter I wrote a few years ago, it is impossible to actually gain
>>>anything by writing a compiler. I didn't understand CL symbols so I
>>>didn't copy them, and they are a Good Idea (TM) if you're going to be
>>>compiling things.
>>
>>"Didn't copy them"?
>>
>>I just wrote a descendant of TLispObject (which makes it Pascal
>>type-safe with other Lisp data and subjects it to reference counting)
>>which contained a symbol name string. A function obtains symbols by
>>name, and symbol equality is determined by performing an object
>>comparison. Will that work?
>
>
> Here's what I did "wrong" ...
>
> I wanted to have one lisp image of sorts that provided separation of
> environment for many users. If you're interested, I wrote it as a
> control language for a space simulation engine that I created for MUSH
> games to use. I wanted to allow the players of the game to use this
> lisp dialect to control their ships and so forth. It's actually very
> successful for that, even if nobody but myself has coded for it (it's
> so much easier to code an autopilot in lisp than in C or, worse,
> MUSHcode). So each ship has to have its own environment, but I didn't
> want one lisp image per ship since it seemed wasteful of memory.
>
> So what I have is a tree structure of "environments." Each one is a
> 4-element vector of the form #(symbols global dynamic parent), where
> symbols is an alist of symbols and values, global is a pointer to the
> global environment (the procedures and such that the language provides
> are in that environment's symbol list), dynamic is a pointer to the
> dynamic environment, which is defined as the local root-level one (so,
> global symbols for the current user), and parent is a pointer to the
> parent environment.
>
> Environments are created when entering a funcall and when entering a
> let body.
>
> The problem with this in terms of compilation is that, unless I make
> it extremely smart (which I'm frankly not willing to spend the time
> doing because I'm not a compiler wizard to begin with and this is just
> a hobby project), the compiler will not be able to do any better than
> compiling to bytecode and placing the symbol name into the output,
> while the bytecode interpreter would have to read the symbol name and
> then look it up through the alists of the environment chain to
> determine the symbol value.
>
> Also, I do not have separate namespaces for variables and functions.
>
> What I would prefer to have done (and it's possible to change to this,
> but involves extensive changes that I don't really have time for, see
> above) is to have at least a value slot on the symbol and then stored
> just a list of symbols rather than an alist of (symbol . value) pairs.
> Essentially, my symbols only have a name and nothing else. (In fact,
> other than parsing, printing, and evaluating, they are the exact same
> underlying C code as for strings.)
>
> Contrast with Common Lisp, where symbols have the following slots:
> Name
> Package
> Value
> Function
> Property list
> ...possibly more that I'm forgetting in this quick post.
>
> Trying to figure out how to implement a compiler for my lisp dialect
> was one of the stepping stones to grokking CL symbols.
Wouldn't the Value and Function slots be stacks of values then, to allow
for lexical variables?
--
The science of economics is the cleverest proof of free will yet
constructed.
Ari Johnson wrote:
> Eli Gottlieb <···········@gmail.com> writes:
>
>
>>Ari Johnson wrote:
>>
>>>Whatever you do, think about compilation before you design your data
>>>structures. Because of how I handled symbol lookups in a lisp
>>>interpreter I wrote a few years ago, it is impossible to actually gain
>>>anything by writing a compiler. I didn't understand CL symbols so I
>>>didn't copy them, and they are a Good Idea (TM) if you're going to be
>>>compiling things.
>>
>>"Didn't copy them"?
>>
>>I just wrote a descendant of TLispObject (which makes it Pascal
>>type-safe with other Lisp data and subjects it to reference counting)
>>which contained a symbol name string. A function obtains symbols by
>>name, and symbol equality is determined by performing an object
>>comparison. Will that work?
>
>
> Here's what I did "wrong" ...
>
> I wanted to have one lisp image of sorts that provided separation of
> environment for many users. If you're interested, I wrote it as a
> control language for a space simulation engine that I created for MUSH
> games to use. I wanted to allow the players of the game to use this
> lisp dialect to control their ships and so forth. It's actually very
> successful for that, even if nobody but myself has coded for it (it's
> so much easier to code an autopilot in lisp than in C or, worse,
> MUSHcode). So each ship has to have its own environment, but I didn't
> want one lisp image per ship since it seemed wasteful of memory.
>
> So what I have is a tree structure of "environments." Each one is a
> 4-element vector of the form #(symbols global dynamic parent), where
> symbols is an alist of symbols and values, global is a pointer to the
> global environment (the procedures and such that the language provides
> are in that environment's symbol list), dynamic is a pointer to the
> dynamic environment, which is defined as the local root-level one (so,
> global symbols for the current user), and parent is a pointer to the
> parent environment.
>
> Environments are created when entering a funcall and when entering a
> let body.
>
> The problem with this in terms of compilation is that, unless I make
> it extremely smart (which I'm frankly not willing to spend the time
> doing because I'm not a compiler wizard to begin with and this is just
> a hobby project), the compiler will not be able to do any better than
> compiling to bytecode and placing the symbol name into the output,
> while the bytecode interpreter would have to read the symbol name and
> then look it up through the alists of the environment chain to
> determine the symbol value.
>
> Also, I do not have separate namespaces for variables and functions.
>
> What I would prefer to have done (and it's possible to change to this,
> but involves extensive changes that I don't really have time for, see
> above) is to have at least a value slot on the symbol and then stored
> just a list of symbols rather than an alist of (symbol . value) pairs.
> Essentially, my symbols only have a name and nothing else. (In fact,
> other than parsing, printing, and evaluating, they are the exact same
> underlying C code as for strings.)
>
> Contrast with Common Lisp, where symbols have the following slots:
> Name
> Package
> Value
> Function
> Property list
> ...possibly more that I'm forgetting in this quick post.
>
> Trying to figure out how to implement a compiler for my lisp dialect
> was one of the stepping stones to grokking CL symbols.
Wouldn't the Value and Function slots be stacks of values then, to allow
for lexical variables?
Also, it appears I made the same mistake you did. Oh, well. I don't
see a way to implement first-class environments with the CL method, anyway.
--
The science of economics is the cleverest proof of free will yet
constructed.
Eli Gottlieb <···········@gmail.com> writes:
> Ari Johnson wrote:
>> Eli Gottlieb <···········@gmail.com> writes:
>>
>>>Ari Johnson wrote:
>>>
>>>>Whatever you do, think about compilation before you design your data
>>>>structures. Because of how I handled symbol lookups in a lisp
>>>>interpreter I wrote a few years ago, it is impossible to actually gain
>>>>anything by writing a compiler. I didn't understand CL symbols so I
>>>>didn't copy them, and they are a Good Idea (TM) if you're going to be
>>>>compiling things.
>>>
>>>"Didn't copy them"?
>>>
>>>I just wrote a descendant of TLispObject (which makes it Pascal
>>>type-safe with other Lisp data and subjects it to reference counting)
>>>which contained a symbol name string. A function obtains symbols by
>>>name, and symbol equality is determined by performing an object
>>>comparison. Will that work?
>> Here's what I did "wrong" ...
>> I wanted to have one lisp image of sorts that provided separation of
>> environment for many users. If you're interested, I wrote it as a
>> control language for a space simulation engine that I created for MUSH
>> games to use. I wanted to allow the players of the game to use this
>> lisp dialect to control their ships and so forth. It's actually very
>> successful for that, even if nobody but myself has coded for it (it's
>> so much easier to code an autopilot in lisp than in C or, worse,
>> MUSHcode). So each ship has to have its own environment, but I didn't
>> want one lisp image per ship since it seemed wasteful of memory.
>> So what I have is a tree structure of "environments." Each one is a
>> 4-element vector of the form #(symbols global dynamic parent), where
>> symbols is an alist of symbols and values, global is a pointer to the
>> global environment (the procedures and such that the language provides
>> are in that environment's symbol list), dynamic is a pointer to the
>> dynamic environment, which is defined as the local root-level one (so,
>> global symbols for the current user), and parent is a pointer to the
>> parent environment.
>> Environments are created when entering a funcall and when entering a
>> let body.
>> The problem with this in terms of compilation is that, unless I make
>> it extremely smart (which I'm frankly not willing to spend the time
>> doing because I'm not a compiler wizard to begin with and this is just
>> a hobby project), the compiler will not be able to do any better than
>> compiling to bytecode and placing the symbol name into the output,
>> while the bytecode interpreter would have to read the symbol name and
>> then look it up through the alists of the environment chain to
>> determine the symbol value.
>> Also, I do not have separate namespaces for variables and functions.
>> What I would prefer to have done (and it's possible to change to
>> this,
>> but involves extensive changes that I don't really have time for, see
>> above) is to have at least a value slot on the symbol and then stored
>> just a list of symbols rather than an alist of (symbol . value) pairs.
>> Essentially, my symbols only have a name and nothing else. (In fact,
>> other than parsing, printing, and evaluating, they are the exact same
>> underlying C code as for strings.)
>> Contrast with Common Lisp, where symbols have the following slots:
>> Name
>> Package
>> Value
>> Function
>> Property list
>> ...possibly more that I'm forgetting in this quick post.
>> Trying to figure out how to implement a compiler for my lisp dialect
>> was one of the stepping stones to grokking CL symbols.
> Wouldn't the Value and Function slots be stacks of values then, to
> allow for lexical variables?
>
> Also, it appears I made the same mistake you did. Oh, well. I don't
> see a way to implement first-class environments with the CL method,
> anyway.
I don't either, although I suspect someone who reads this eventually
will. :)
Eli Gottlieb wrote:
> Also, it appears I made the same mistake you did. Oh, well. I don't
> see a way to implement first-class environments with the CL method, anyway.
Both OpenLisp and clisp provide first-class lexical environments (clisp
only for interpreted code).
Pascal
--
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/
Pascal Costanza wrote:
> Eli Gottlieb wrote:
>
>> Also, it appears I made the same mistake you did. Oh, well. I don't
>> see a way to implement first-class environments with the CL method,
>> anyway.
>
>
> Both OpenLisp and clisp provide first-class lexical environments (clisp
> only for interpreted code).
>
>
> Pascal
>
So how do I use them in clisp? Didn't even know they were there.
Still, with first-class environments built into the language I can do this:
(define defun (quoted-lambda (name args body) (eval `(define ,name
(lambda ,args ,body)))))
(define defquoted (quoted-lambda (name args body) (eval `(define ,name
(quoted-lambda ,args ,body)))))
(defquoted macro (args body) (eval `(quoted-lambda ,args (eval ,body
(parent-env (current-env) 'caller))) (parent-env (current-env) 'caller)))
(defquoted defmacro (name args body) (eval `(define ,name (macro ,args
,body))))
(defmacro if (condition then &optional else) `(cond (,condition ,then)
(t ,else)))
;This version of list guarantees a freshly consed result.
(defun list (&rest items) (if items (cons (car items) (apply list (cdr
items)))))
;This version just returns its argument. If that argument was obtained
from the use of APPLY, its list structure could be shared.
(defun other-list (&rest items) items)
Noted that a quoted-lambda simply creates a function whose arguments are
not evaluated prior to being passed in, it's as though they were all
given one level of quoting. Defquoted defines a quoted-lambda function
in the dynamic environment with the given args and body, and the
environment functions are used to make sure the bindings of name, args
and body don't interfere with the code passed into those parameters by
passing them as an optional argument to eval. If no environment is
given (as in the def* forms) eval uses the dynamic environment by default.
--
The science of economics is the cleverest proof of free will yet
constructed.
Eli Gottlieb wrote:
> Pascal Costanza wrote:
>> Eli Gottlieb wrote:
>>
>>> Also, it appears I made the same mistake you did. Oh, well. I don't
>>> see a way to implement first-class environments with the CL method,
>>> anyway.
>>
>>
>> Both OpenLisp and clisp provide first-class lexical environments
>> (clisp only for interpreted code).
>>
>>
>> Pascal
>>
> So how do I use them in clisp? Didn't even know they were there.
See http://clisp.cons.org/impnotes.html#eval-environ
Pascal
--
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/
Pascal Costanza wrote:
> Eli Gottlieb wrote:
>
>> Pascal Costanza wrote:
>>
>>> Eli Gottlieb wrote:
>>>
>>>> Also, it appears I made the same mistake you did. Oh, well. I
>>>> don't see a way to implement first-class environments with the CL
>>>> method, anyway.
>>>
>>>
>>>
>>> Both OpenLisp and clisp provide first-class lexical environments
>>> (clisp only for interpreted code).
>>>
>>>
>>> Pascal
>>>
>> So how do I use them in clisp? Didn't even know they were there.
>
>
> See http://clisp.cons.org/impnotes.html#eval-environ
>
>
> Pascal
>
Pity it seems to be uncompilable.
--
The science of economics is the cleverest proof of free will yet
constructed.
Eli Gottlieb wrote:
> Pascal Costanza wrote:
>> Eli Gottlieb wrote:
>>
>>> Pascal Costanza wrote:
>>>
>>>> Eli Gottlieb wrote:
>>>>
>>>>> Also, it appears I made the same mistake you did. Oh, well. I
>>>>> don't see a way to implement first-class environments with the CL
>>>>> method, anyway.
>>>>
>>>> Both OpenLisp and clisp provide first-class lexical environments
>>>> (clisp only for interpreted code).
>>>>
>>>> Pascal
>>>>
>>> So how do I use them in clisp? Didn't even know they were there.
>>
>> See http://clisp.cons.org/impnotes.html#eval-environ
>>
>> Pascal
>>
> Pity it seems to be uncompilable.
...but it's good enough to play around with this. OpenLisp provides the
same constructs, but if I understand correctly, can still be compiled.
Note, however, that it's pretty hard to impossible anyway to do decent
compilation once you introduce first-class lexical environments. Lookup
of variables has to go through an environment structure (an alist or a
hashtable) and this is slower than lexical addressing. Due to macros, a
capture of a lexical environment can happen almost anywhere (as part of
the code that a macro expands to).
The kind of reflection that first-class environments provide can also be
achieved in a relatively straightforward way with different means.
OOP-style objects or structs can, for example, be used to group state
and pass it around. If you really want more seamless access to a lexical
environment, you can also use plain closures, roughly like this:
(let ((a b c))
(lambda (op var &optional value)
(ecase op
(get (ecase var (a a) (b b) (c c)))
(set (ecase var
(a (setq a value))
(b (setq b value))
(c (setq c value)))))))
The overhead that is introduced by looking up a variable in an ecase
form is probably roughly the overhead you would get by looking up
variables in a first-class lexical environment.
Compare this to the use of a struct:
(defstruct env a b c)
(let ((env (make-env :a 0 :b 1 :c 2)))
env)
If you get such an environment object you can access its slots like this:
(env-a env)
(setf (env-b env) 42)
Such access to slots in structs (or OOP-style objects) is very likely
much faster than access to variables in first-class environments.
Pascal
--
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/
Pascal Costanza wrote:
> Eli Gottlieb wrote:
>
>> Pascal Costanza wrote:
>>
>>> Eli Gottlieb wrote:
>>>
>>>> Pascal Costanza wrote:
>>>>
>>>>> Eli Gottlieb wrote:
>>>>>
>>>>>> Also, it appears I made the same mistake you did. Oh, well. I
>>>>>> don't see a way to implement first-class environments with the CL
>>>>>> method, anyway.
>>>>>
>>>>>
>>>>> Both OpenLisp and clisp provide first-class lexical environments
>>>>> (clisp only for interpreted code).
>>>>>
>>>>> Pascal
>>>>>
>>>> So how do I use them in clisp? Didn't even know they were there.
>>>
>>>
>>> See http://clisp.cons.org/impnotes.html#eval-environ
>>>
>>> Pascal
>>>
>> Pity it seems to be uncompilable.
>
>
> ...but it's good enough to play around with this. OpenLisp provides the
> same constructs, but if I understand correctly, can still be compiled.
>
> Note, however, that it's pretty hard to impossible anyway to do decent
> compilation once you introduce first-class lexical environments. Lookup
> of variables has to go through an environment structure (an alist or a
> hashtable) and this is slower than lexical addressing. Due to macros, a
> capture of a lexical environment can happen almost anywhere (as part of
> the code that a macro expands to).
>
> The kind of reflection that first-class environments provide can also be
> achieved in a relatively straightforward way with different means.
> OOP-style objects or structs can, for example, be used to group state
> and pass it around. If you really want more seamless access to a lexical
> environment, you can also use plain closures, roughly like this:
>
> (let ((a b c))
> (lambda (op var &optional value)
> (ecase op
> (get (ecase var (a a) (b b) (c c)))
> (set (ecase var
> (a (setq a value))
> (b (setq b value))
> (c (setq c value)))))))
>
> The overhead that is introduced by looking up a variable in an ecase
> form is probably roughly the overhead you would get by looking up
> variables in a first-class lexical environment.
>
> Compare this to the use of a struct:
>
> (defstruct env a b c)
>
> (let ((env (make-env :a 0 :b 1 :c 2)))
> env)
>
> If you get such an environment object you can access its slots like this:
>
> (env-a env)
> (setf (env-b env) 42)
>
> Such access to slots in structs (or OOP-style objects) is very likely
> much faster than access to variables in first-class environments.
>
>
> Pascal
>
Using the lambdas to capture variables works more efficiently, but only
for variables specified at time of writing. First-class lexical
environments allow lookup of any variable that environment was able to
see, without having to know what it was/is ahead of time.
And who says variable lookup in a lexical environment has to go through
a Lisp hash table or alist? I store and look up bindings in mine at the
Pascal level (going to add an optimized structure like a hash table for
them one of these days), and the expressive power is still the same.
Actually, if your lexical environment system has a new-env function to
create an empty environment (or a binding to an empty environment in the
dynamic/global environment), you can use that, let forms, and
evaluation-in-given-environment to build the struct and object system.
;Assuming blithely for the moment that a class is just a list of slot
names stored as symbols.
(defun allocate-slots (slots)
(eval
`(let ,(mapcar list slots)
(current-env))
(new-env)))
This returns an environment object with the given slots. Note that the
"list" would read "#'list" in CL.
--
The science of economics is the cleverest proof of free will yet
constructed.
Eli Gottlieb wrote:
> Pascal Costanza wrote:
>> Eli Gottlieb wrote:
>>
>>> Pascal Costanza wrote:
>>>
>>>> Eli Gottlieb wrote:
>>>>
>>>>> Pascal Costanza wrote:
>>>>>
>>>>>> Eli Gottlieb wrote:
>>>>>>
>>>>>>> Also, it appears I made the same mistake you did. Oh, well. I
>>>>>>> don't see a way to implement first-class environments with the CL
>>>>>>> method, anyway.
>>>>>>
>>>>>>
>>>>>> Both OpenLisp and clisp provide first-class lexical environments
>>>>>> (clisp only for interpreted code).
>>>>>>
>>>>>> Pascal
>>>>>>
>>>>> So how do I use them in clisp? Didn't even know they were there.
>>>>
>>>>
>>>> See http://clisp.cons.org/impnotes.html#eval-environ
>>>>
>>>> Pascal
>>>>
>>> Pity it seems to be uncompilable.
>>
>>
>> ...but it's good enough to play around with this. OpenLisp provides
>> the same constructs, but if I understand correctly, can still be
>> compiled.
>>
>> Note, however, that it's pretty hard to impossible anyway to do decent
>> compilation once you introduce first-class lexical environments.
>> Lookup of variables has to go through an environment structure (an
>> alist or a hashtable) and this is slower than lexical addressing. Due
>> to macros, a capture of a lexical environment can happen almost
>> anywhere (as part of the code that a macro expands to).
>>
>> The kind of reflection that first-class environments provide can also
>> be achieved in a relatively straightforward way with different means.
>> OOP-style objects or structs can, for example, be used to group state
>> and pass it around. If you really want more seamless access to a
>> lexical environment, you can also use plain closures, roughly like this:
>>
>> (let ((a b c))
>> (lambda (op var &optional value)
>> (ecase op
>> (get (ecase var (a a) (b b) (c c)))
>> (set (ecase var
>> (a (setq a value))
>> (b (setq b value))
>> (c (setq c value)))))))
>>
>> The overhead that is introduced by looking up a variable in an ecase
>> form is probably roughly the overhead you would get by looking up
>> variables in a first-class lexical environment.
>>
>> Compare this to the use of a struct:
>>
>> (defstruct env a b c)
>>
>> (let ((env (make-env :a 0 :b 1 :c 2)))
>> env)
>>
>> If you get such an environment object you can access its slots like this:
>>
>> (env-a env)
>> (setf (env-b env) 42)
>>
>> Such access to slots in structs (or OOP-style objects) is very likely
>> much faster than access to variables in first-class environments.
>>
>> Pascal
>>
> Using the lambdas to capture variables works more efficiently, but only
> for variables specified at time of writing. First-class lexical
> environments allow lookup of any variable that environment was able to
> see, without having to know what it was/is ahead of time.
You can achieve the same effect by using a code walker. After all, the
lexical environment is by definition quite static. I am not convinced
that you gain a lot by being able to access arbitrary bindings in a
first-class lexical environment.
> And who says variable lookup in a lexical environment has to go through
> a Lisp hash table or alist? I store and look up bindings in mine at the
> Pascal level (going to add an optimized structure like a hash table for
> them one of these days), and the expressive power is still the same.
What you want for performance is lexical addressing, that is, fixed
offsets that can be used relative to the base address of a stack frame.
Since your lexical environments can potentially come from anywhere in
code like (eval something some-environment), you cannot use fixed
offsets anymore, so you will have to do a translation from variable
names to offsets at runtime. This is the runtime overhead that I am
talking about here.
> Actually, if your lexical environment system has a new-env function to
> create an empty environment (or a binding to an empty environment in the
> dynamic/global environment), you can use that, let forms, and
> evaluation-in-given-environment to build the struct and object system.
>
> ;Assuming blithely for the moment that a class is just a list of slot
> names stored as symbols.
> (defun allocate-slots (slots)
> (eval
> `(let ,(mapcar list slots)
> (current-env))
> (new-env)))
I don't see how this helps you (or what this helps you with).
Pascal
--
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/
Pascal Costanza wrote:
> Eli Gottlieb wrote:
>
>> Pascal Costanza wrote:
>>
>>> Eli Gottlieb wrote:
>>>
>>>> Pascal Costanza wrote:
>>>>
>>>>> Eli Gottlieb wrote:
>>>>>
>>>>>> Pascal Costanza wrote:
>>>>>>
>>>>>>> Eli Gottlieb wrote:
>>>>>>>
>>>>>>>> Also, it appears I made the same mistake you did. Oh, well. I
>>>>>>>> don't see a way to implement first-class environments with the
>>>>>>>> CL method, anyway.
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> Both OpenLisp and clisp provide first-class lexical environments
>>>>>>> (clisp only for interpreted code).
>>>>>>>
>>>>>>> Pascal
>>>>>>>
>>>>>> So how do I use them in clisp? Didn't even know they were there.
>>>>>
>>>>>
>>>>>
>>>>> See http://clisp.cons.org/impnotes.html#eval-environ
>>>>>
>>>>> Pascal
>>>>>
>>>> Pity it seems to be uncompilable.
>>>
>>>
>>>
>>> ...but it's good enough to play around with this. OpenLisp provides
>>> the same constructs, but if I understand correctly, can still be
>>> compiled.
>>>
>>> Note, however, that it's pretty hard to impossible anyway to do
>>> decent compilation once you introduce first-class lexical
>>> environments. Lookup of variables has to go through an environment
>>> structure (an alist or a hashtable) and this is slower than lexical
>>> addressing. Due to macros, a capture of a lexical environment can
>>> happen almost anywhere (as part of the code that a macro expands to).
>>>
>>> The kind of reflection that first-class environments provide can also
>>> be achieved in a relatively straightforward way with different means.
>>> OOP-style objects or structs can, for example, be used to group state
>>> and pass it around. If you really want more seamless access to a
>>> lexical environment, you can also use plain closures, roughly like this:
>>>
>>> (let ((a b c))
>>> (lambda (op var &optional value)
>>> (ecase op
>>> (get (ecase var (a a) (b b) (c c)))
>>> (set (ecase var
>>> (a (setq a value))
>>> (b (setq b value))
>>> (c (setq c value)))))))
>>>
>>> The overhead that is introduced by looking up a variable in an ecase
>>> form is probably roughly the overhead you would get by looking up
>>> variables in a first-class lexical environment.
>>>
>>> Compare this to the use of a struct:
>>>
>>> (defstruct env a b c)
>>>
>>> (let ((env (make-env :a 0 :b 1 :c 2)))
>>> env)
>>>
>>> If you get such an environment object you can access its slots like
>>> this:
>>>
>>> (env-a env)
>>> (setf (env-b env) 42)
>>>
>>> Such access to slots in structs (or OOP-style objects) is very likely
>>> much faster than access to variables in first-class environments.
>>>
>>> Pascal
>>>
>> Using the lambdas to capture variables works more efficiently, but
>> only for variables specified at time of writing. First-class lexical
>> environments allow lookup of any variable that environment was able to
>> see, without having to know what it was/is ahead of time.
>
>
> You can achieve the same effect by using a code walker. After all, the
> lexical environment is by definition quite static. I am not convinced
> that you gain a lot by being able to access arbitrary bindings in a
> first-class lexical environment.
Because everyone loves writing code walkers, loves having second-class
compile-time macros that often require gensyms, and loves coming up with
complicated macros of the aforementioned sort for the smallest task.
>
>> And who says variable lookup in a lexical environment has to go
>> through a Lisp hash table or alist? I store and look up bindings in
>> mine at the Pascal level (going to add an optimized structure like a
>> hash table for them one of these days), and the expressive power is
>> still the same.
>
>
> What you want for performance is lexical addressing, that is, fixed
> offsets that can be used relative to the base address of a stack frame.
> Since your lexical environments can potentially come from anywhere in
> code like (eval something some-environment), you cannot use fixed
> offsets anymore, so you will have to do a translation from variable
> names to offsets at runtime. This is the runtime overhead that I am
> talking about here.
'Tis true, alas.
>
>> Actually, if your lexical environment system has a new-env function to
>> create an empty environment (or a binding to an empty environment in
>> the dynamic/global environment), you can use that, let forms, and
>> evaluation-in-given-environment to build the struct and object system.
>>
>> ;Assuming blithely for the moment that a class is just a list of slot
>> names stored as symbols.
>> (defun allocate-slots (slots)
>> (eval
>> `(let ,(mapcar list slots)
>> (current-env))
>> (new-env)))
>
>
> I don't see how this helps you (or what this helps you with).
>
>
> Pascal
>
Remember the bit above where I defined a macro as an evaluation of the
body form in the caller's environment? In CL, macros are both
second-class citizens and have to be built into the language. In my
Lisp, macros are first-class and are defined as special case of a
function. Will you consider that, at least, an increase in expressive
power?
Here's a version of Peter Seibel's "doprimes" macro (from PCL chapter 8)
that requires absolutely no gensyms because of how macros work in my Lisp:
> (defmacro doprimes (var-start-end &rest body)
> (let ((var (car var-start-end)) (start (eval (cadr var-start-end) (caller-env))) (end (eval (caddr var-start-end) (caller-env))))
> `(do ((,var (next-prime ,start) (next-prime (+1 ,var))))
> ((> ,var ,end))
> ,@body)))
Note how there's no need for either gensym or the ending-value variable.
The macro is called at runtime, and can thus evaluate both start and
end in the appropriate order /outside the quasiquote expression/. This
means the do loop and body never see the start or end bindings, thus
doing away with gensyms.
Sorry about the argument list, though. I haven't taught the thing to
deconstruct lists quite yet.
--
The science of economics is the cleverest proof of free will yet
constructed.
Eli Gottlieb wrote:
> Because everyone loves writing code walkers, loves having second-class
> compile-time macros that often require gensyms, and loves coming up with
> complicated macros of the aforementioned sort for the smallest task.
Adendum to Usenet posting: Also, having first-class environments allows
my to trivially define a locative (which will never go null, it counts
for garbage collection/reference counting):
(defun locative (symbol)
(list symbol (caller-env)))
(defun dereference (loc)
(eval (car loc) (cadr loc)))
--
The science of economics is the cleverest proof of free will yet
constructed.
Eli Gottlieb wrote:
> Pascal Costanza wrote:
>> Eli Gottlieb wrote:
>>
>>> Using the lambdas to capture variables works more efficiently, but
>>> only for variables specified at time of writing. First-class lexical
>>> environments allow lookup of any variable that environment was able
>>> to see, without having to know what it was/is ahead of time.
>>
>> You can achieve the same effect by using a code walker. After all, the
>> lexical environment is by definition quite static. I am not convinced
>> that you gain a lot by being able to access arbitrary bindings in a
>> first-class lexical environment.
>
> Because everyone loves writing code walkers, loves having second-class
> compile-time macros that often require gensyms, and loves coming up with
> complicated macros of the aforementioned sort for the smallest task.
What tasks do you have in mind?
>>> Actually, if your lexical environment system has a new-env function
>>> to create an empty environment (or a binding to an empty environment
>>> in the dynamic/global environment), you can use that, let forms, and
>>> evaluation-in-given-environment to build the struct and object system.
>>>
>>> ;Assuming blithely for the moment that a class is just a list of slot
>>> names stored as symbols.
>>> (defun allocate-slots (slots)
>>> (eval
>>> `(let ,(mapcar list slots)
>>> (current-env))
>>> (new-env)))
>>
>> I don't see how this helps you (or what this helps you with).
>>
>>
>> Pascal
>>
> Remember the bit above where I defined a macro as an evaluation of the
> body form in the caller's environment? In CL, macros are both
> second-class citizens and have to be built into the language. In my
> Lisp, macros are first-class and are defined as special case of a
> function. Will you consider that, at least, an increase in expressive
> power?
Yes, it is an increase in expressive power, but it comes at a cost.
Basically, your language becomes mostly not compilable anymore. Since
you don't know what a first-class macro will do to some piece of code,
you cannot compile that piece of code anymore, but you have to defer
this to runtime.
> Here's a version of Peter Seibel's "doprimes" macro (from PCL chapter 8)
> that requires absolutely no gensyms because of how macros work in my Lisp:
>> (defmacro doprimes (var-start-end &rest body)
>> (let ((var (car var-start-end)) (start (eval (cadr var-start-end)
>> (caller-env))) (end (eval (caddr var-start-end) (caller-env))))
>> `(do ((,var (next-prime ,start) (next-prime (+1 ,var))))
>> ((> ,var ,end))
>> ,@body)))
> Note how there's no need for either gensym or the ending-value variable.
> The macro is called at runtime, and can thus evaluate both start and
> end in the appropriate order /outside the quasiquote expression/. This
> means the do loop and body never see the start or end bindings, thus
> doing away with gensyms.
>
> Sorry about the argument list, though. I haven't taught the thing to
> deconstruct lists quite yet.
Check out literature on reflection and especially 3-Lisp - they
basically already had such a model. If you can, grab a copy of the book
"Meta-Level Architectures and Reflection" by Maes & Nardi, it has an
excellent overview article by Jim des Rivieres about these things.
Basically, 3-Lisp defines an nlambda form that takes unevaluated
arguments as s-expressions, the environment in which the function was
called and a continuation. With this you can implement all kinds of
control structures. An if-statement would look like this:
(define if (nlambda (args env cont)
(cont (cond ((eval (first args) env)
(eval (second args) env))
(t (eval (third args) env))))))
[Don't rely on the syntactic details here, I am reconstructing this
without having the book at hand.]
I am not sure what this all buys you - there are already quite decent
reflective features in Common Lisp which can be implemented very
efficiently at the same time. Just my 0.02�.
Pascal
--
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/
Pascal Costanza wrote:
> I am not sure what this all buys you - there are already quite decent
> reflective features in Common Lisp which can be implemented very
> efficiently at the same time. Just my 0.02�.
>
>
> Pascal
>
>
Are these reflective features of a Common Lisp /implementation/ or
reflective features of Common Lisp /the language/?
--
The science of economics is the cleverest proof of free will yet
constructed.
Eli Gottlieb wrote:
> Pascal Costanza wrote:
>> I am not sure what this all buys you - there are already quite decent
>> reflective features in Common Lisp which can be implemented very
>> efficiently at the same time. Just my 0.02�.
>>
>> Pascal
>>
> Are these reflective features of a Common Lisp /implementation/ or
> reflective features of Common Lisp /the language/?
It's hard to answer this question since you haven't responded to my
question what the tasks are that you have in mind. (or I have missed
your response)
Some of the reflective features are already part of the language
specification, some are semi-standard and some are implementation-specific.
Pascal
--
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/
Eli Gottlieb <···········@gmail.com> writes:
> First-class lexical
> environments allow lookup of any variable that environment was able to
> see, without having to know what it was/is ahead of time.
That is at odds with many an optimisation that typical compilers do:
+ prove that a variable's value has so limited lifetime that it can
be put to a register
+ prove that scope is limited so the storage cell can be reused for
another object
Providing first class environment to be able to MODIFY variables looks
like gcc -O0 = disable all optimizations. Then play with gdb.
In CLISP, this translates to: "use the interpreter, not the compiler."
However, many people want a compiler. Are they all fools looking for
a false graal called "performance"?
> requires absolutely no gensyms because of how macros work in my Lisp:
> > (defmacro doprimes (var-start-end &rest body)
> > (let ((var (car var-start-end)) (start (eval (cadr var-start-end) (caller-env))) (end (eval (caddr var-start-end) (caller-env))))
> > `(do ((,var (next-prime ,start) (next-prime (+1 ,var))))
> > ((> ,var ,end))
> > ,@body)))
> Note how there's no need for either gensym or the ending-value variable.
In my perception, GENSYM is a good enough solution to variable capture
on a historic path that lead to lexical scoping and compiler optimizations.
Your introducing EVAL is anti-news. News taken backwards.
For a humorous example, read Emacs' ANTINEWS file (found in every distro).
E.g. "Removed the garbage collector because nobody understood it" :-)
EVAL is precisely what Lispers went away from, thirty years ago
already, for most uses.
Your work looks to me like a proof that history goes in circles.
Yet maybe there's a niche market for "simple Lisps"?
Regards,
Jorg Hohle
Telekom/T-Systems Technology Center
Joerg Hoehle wrote:
> Eli Gottlieb <···········@gmail.com> writes:
>
>
>>First-class lexical
>>environments allow lookup of any variable that environment was able to
>>see, without having to know what it was/is ahead of time.
>
>
> That is at odds with many an optimisation that typical compilers do:
> + prove that a variable's value has so limited lifetime that it can
> be put to a register
> + prove that scope is limited so the storage cell can be reused for
> another object
>
> Providing first class environment to be able to MODIFY variables looks
> like gcc -O0 = disable all optimizations. Then play with gdb.
> In CLISP, this translates to: "use the interpreter, not the compiler."
>
> However, many people want a compiler. Are they all fools looking for
> a false grail called "performance"?
>
No, they are not fools to desire performance. I'm just not designing
for them, I'm designing for a guy who wants to sit at his machine and
hack, rather than write Yet Another Brilliant Web Application. If you
want a dialect for YABWA, go nag Paul Graham.
> In my perception, GENSYM is a good enough solution to variable capture
> on a historic path that lead to lexical scoping and compiler optimizations.
>
> Your introducing EVAL is anti-news. News taken backwards.
> For a humorous example, read Emacs' ANTINEWS file (found in every distro).
> E.g. "Removed the garbage collector because nobody understood it" :-)
> EVAL is precisely what Lispers went away from, thirty years ago
> already, for most uses.
> Your work looks to me like a proof that history goes in circles.
Precisely what necessitates adding things to the language *just* to get
around ever (horror of horrors!) calling eval?
>
> Yet maybe there's a niche market for "simple Lisps"?
>
> Regards,
> Jorg Hohle
> Telekom/T-Systems Technology Center
Market? I'm supposed to be oriented towards a market now?
--
The science of economics is the cleverest proof of free will yet
constructed.
Eli Gottlieb <···········@gmail.com> writes:
> Also, it appears I made the same mistake you did. Oh, well. I don't
> see a way to implement first-class environments with the CL method,
> anyway.
Speaking of making the same mistake that I did, if you'd like a copy
of my Lisp system let me know. It sounds like you're already far
beyond anything I did with it, but the offer's there if you'd like it.
Ari Johnson wrote:
> Eli Gottlieb <···········@gmail.com> writes:
>
>
>>Also, it appears I made the same mistake you did. Oh, well. I don't
>>see a way to implement first-class environments with the CL method,
>>anyway.
>
>
> Speaking of making the same mistake that I did, if you'd like a copy
> of my Lisp system let me know. It sounds like you're already far
> beyond anything I did with it, but the offer's there if you'd like it.
How far did you get along with it? Your previous post stated that for
writing autopilot programs it was extremely successful!
--
The science of economics is the cleverest proof of free will yet
constructed.
Eli Gottlieb <···········@gmail.com> writes:
> Ari Johnson wrote:
>> Eli Gottlieb <···········@gmail.com> writes:
>>
>>>Also, it appears I made the same mistake you did. Oh, well. I don't
>>>see a way to implement first-class environments with the CL method,
>>>anyway.
>> Speaking of making the same mistake that I did, if you'd like a copy
>> of my Lisp system let me know. It sounds like you're already far
>> beyond anything I did with it, but the offer's there if you'd like it.
> How far did you get along with it? Your previous post stated that for
> writing autopilot programs it was extremely successful!
It's very workable and, other than adding a bytecode compiler, somehow
making it thread-safe, adding data types[1], or a couple of other
improvements, I can't think of how to make it more useful for the
purposes it serves.
The system it's plugged into adds a few data types and quite a few
procedures to make it all work. But it's a fairly decent minimalist
scripting/extension language for places you want to have one but don't
want to give its users dangerous powers like being able to open files
on disk and so forth (which is why I didn't use Guile or even Ruby for
this purpose).
E-mail me if you're interested in a copy. It's only about 4450 lines
of C.
[1] - It has int, float, symbol, string, vector, procedure (coded in
C), macro (coded in C), closure (coded in lisp), usermacro (coded in
lisp), error (throw your own), stream (with no way to make a new one
in lisp - but only because I deliberately did not provide functions
for that), and space for 2^29 user-defined types - it could stand to
add a bignum type and structs or OOP.