Hi,
I have experimented a little bit, and I think I have found a way how to
embed Lisp-1 into Lisp-2, or rather: how to get the effects that make
Lisp-1 convenient in some circumstances.
Instead of changing the behavior of variables or introducing unifying
environments I have just written a macro that turns all function forms
inside of it into equivalent funcalled expressions.
The gist of the idea is that you need to declare in the macro which
functions you want to "import" into this reduced namespace. Here is an
example:
(with-lisp-1 (print list)
(let ((f (lambda (x) (+ x x))))
(print (list (f 1) (f 2) (f 3)))))
The first parameter to with-lisp-1 names the functions that get bound to
variables of the same names. Here is what the above example is (roughly)
macroexpanded into:
(let ((print 'print) (list 'list))
(let ((f (lambda (x) (+ x x))))
(funcall print (funcall list (funcall f 1) (funcall f 2) (funcall f
3)))))
Another idea here is that if you defun a function inside of a
with-lisp-1 macro you need to import the respective symbol beforehand.
(That's why the variables get bound to quoted names, not to sharp-quoted
ones.)
The code _seems_ to work on MCL, but I haven't tested it thoroughly.
Especially I haven't extensively tested the handling of special
operators.
I am a little bit worried about this code for two reasons:
- The ANSI spec explicitly advises us not to rely on redefined
macroexpand hooks for the correctness of a program. I think my hook
function is safe but I am not sure. Opinions?
- I don't have enough knowledge in order to predict in which ways this
code may break a Lisp environment.
So, what do you think?
Pascal
(defvar *original-macroexpand-hook* *macroexpand-hook*)
(setq *macroexpand-hook* *original-macroexpand-hook*)
(defmacro with-lisp-1 (functions &environment env &body body)
(labels ((functify (form)
(if (consp form)
(cond ((symbolp (car form))
(cond ((eq 'declare (car form))
form)
((special-operator-p (car form))
(funky-special form))
((macro-function (car form) env)
form)
(t `(funcall ,(car form) ,@(mapcar
#'functify (cdr form))))))
((consp (car form))
(if (eq 'lambda (caar form))
`((lambda ,(cadar form) ,@(mapcar #'functify
(cddar form)))
,@(mapcar #'functify (cdr form)))
`(funcall ,(car form) ,@(mapcar #'functify (cdr
form)))))
(t (error "invalid form ~A" form)))
form))
(funky-special (form)
(ccase (car form)
((block eval-when multiple-value-call return-from the)
(destructuring-bind
(op non-eval &rest forms) form
`(,op ,non-eval ,@(mapcar #'functify forms))))
((flet labels macrolet)
(destructuring-bind
(op defs &rest forms) form
`(,op ,(mapcar (lambda (def)
(destructuring-bind
(name args &rest forms) def
`(,name ,args ,@(mapcar #'functify
forms))))
defs)
,@(mapcar #'functify forms))))
((function go load-time-value quote) form)
((catch if locally multiple-value-prog1 progn progv
tagbody throw unwind-protect)
(destructuring-bind
(op &rest forms) form
`(,op ,@(mapcar #'functify forms))))
((let let*)
(destructuring-bind
(op defs &rest forms) form
`(,op ,(mapcar (lambda (def)
(if (consp def)
`(,(car def) ,(functify (cadr def)))
def))
defs)
,@(mapcar #'functify forms))))
(setq
(destructuring-bind
(op sets) form
`(,op ,@(let (switch)
(mapcar (lambda (elem)
(setf switch (not switch))
(if switch elem (functify elem)))
sets)))))
(symbol-macrolet
(destructuring-bind
(op defs &rest forms) form
`(,op ,(mapcar (lambda (def)
(destructuring-bind
(sym form) def
`(,sym ,(functify form))))
defs)
,@(mapcar #'functify forms)))))))
`(let ,(mapcar (lambda (function)
`(,function ',function)) functions)
(macrolet ((protect-macro (form) form))
,@(mapcar #'functify body)))))
(setq *macroexpand-hook*
(lambda (expander form env)
(if (macro-function (car form) env)
(let ((result (funcall *original-macroexpand-hook* expander
form env)))
(if (not (or (macro-function 'protect-macro env)
(eq 'with-lisp-1 (car form))))
`(with-lisp-1 () ,result)
result))
form)))
Pascal Costanza wrote:
> - The ANSI spec explicitly advises us not to rely on redefined
> macroexpand hooks for the correctness of a program. I think my hook
> function is safe but I am not sure. Opinions?
There is this comment on the *macroexpand-hook* page:
Users or user programs can assign this variable to customize
or trace the macro expansion mechanism. Note, however, that this
variable is a global resource, potentially shared by multiple
programs; as such, if any two programs depend for their correctness
on the setting of this variable, those programs may not be able
to run in the same Lisp image. For this reason, it is frequently
best to confine its uses to debugging situations.
This strikes me as a style guideline, not a prohibition, so you
should be ok as long as you keep the warning in mind.
Paul
In article <······························@news.netcologne.de>, Pascal
Costanza <········@web.de> wrote:
> (with-lisp-1 (print list)
> (let ((f (lambda (x) (+ x x))))
> (print (list (f 1) (f 2) (f 3)))))
>
> The first parameter to with-lisp-1 names the functions that get bound to
> variables of the same names. Here is what the above example is (roughly)
> macroexpanded into:
>
> (let ((print 'print) (list 'list))
> (let ((f (lambda (x) (+ x x))))
> (funcall print (funcall list (funcall f 1) (funcall f 2) (funcall f
> 3)))))
This implementation seems unnecessarily complex. Why go to the trouble of
changing *all* of the function calls to funcalls? This makes more work
for you, the implementor, because you have to write a code walker to do
the transormation, and it also places a burden on the user to declare
which lisp2 functions he's going to call inside the body.
The problem is manifest in your examples:
(with-lisp-1 (print list)
(let ((f (lambda (x) (+ x x))))
(print (list (f 1) (f 2) (f 3)))))
Here it is actually F that is treated differently than usual, and yet it
is PRINT and LIST that the user needs to call out to be handled
differently.
It would be easier to implement, and also I think easier to use,
replacements for Lisp's binding forms that had Lisp1 semantics. You could
even make a package where these forms shadowed CL's originals, so that you
could actually write (what looks like) common lisp code with lisp1
semantics.
The only thing this solution won't let you do is use ((f ...) ...)
syntax. For that you do need a code walker (or a compiler hack).
E.
In article <····················@192.168.1.51>,
···@jpl.nasa.gov (Erann Gat) wrote:
> The only thing this solution won't let you do is use ((f ...) ...)
> syntax. For that you do need a code walker (or a compiler hack).
...but I think that's a crucial part of the whole thing. At least, the
examples I have seen that included such expressions have convinced me
that Lisp-1-ness could be a worthwhile addon.
Pascal
In article <······························@news.netcologne.de>, Pascal
Costanza <········@web.de> wrote:
> In article <····················@192.168.1.51>,
> ···@jpl.nasa.gov (Erann Gat) wrote:
>
> > The only thing this solution won't let you do is use ((f ...) ...)
> > syntax. For that you do need a code walker (or a compiler hack).
>
> ...but I think that's a crucial part of the whole thing. At least, the
> examples I have seen that included such expressions have convinced me
> that Lisp-1-ness could be a worthwhile addon.
You are confusing two orthogonal issues. Lisp1-ness has nothing to do
with the handling of ((f ...) ...), it only has to do with the handling of
(f ...). You can have a Lisp2 (or LispN) that handles ((f ...) ...) (as
you have if you e.g. simply apply a compiler hack to Common Lisp) or you
can have a Lisp1 that doesn't (as you would have if you replaced all the
binding forms). They are completely independent design decisions.
E.
In article <····················@192.168.1.51>,
···@jpl.nasa.gov (Erann Gat) wrote:
> In article <······························@news.netcologne.de>, Pascal
> Costanza <········@web.de> wrote:
>
> > In article <····················@192.168.1.51>,
> > ···@jpl.nasa.gov (Erann Gat) wrote:
> >
> > > The only thing this solution won't let you do is use ((f ...) ...)
> > > syntax. For that you do need a code walker (or a compiler hack).
> >
> > ...but I think that's a crucial part of the whole thing. At least, the
> > examples I have seen that included such expressions have convinced me
> > that Lisp-1-ness could be a worthwhile addon.
>
> You are confusing two orthogonal issues. Lisp1-ness has nothing to do
> with the handling of ((f ...) ...), it only has to do with the handling of
> (f ...). You can have a Lisp2 (or LispN) that handles ((f ...) ...) (as
> you have if you e.g. simply apply a compiler hack to Common Lisp) or you
> can have a Lisp1 that doesn't (as you would have if you replaced all the
> binding forms). They are completely independent design decisions.
Agreed. But I think that for the programming style I have in mind, both
ingredients are important. Of course, the name with-lisp-1 is a
misnomer, and I will change that.
In Scheme, the essential idea is that all positions in an sexpr are
treated equally, and then you need both Lisp-1-ness and treatment of
conses in the first position. This was the aha effect I didn't have
until very recently - the dispute between Schemers and Common Lispniks
is not solely the Lisp-1 vs. Lisp-2 issue, although it often seems to be
reduced to that question.
Pascal
From: Dorai Sitaram
Subject: Re: Lisp-1 vs. Lisp-2: A possible solution
Date:
Message-ID: <bbd4gn$fje$1@news.gte.com>
In article <······························@news.netcologne.de>,
Pascal Costanza <········@web.de> wrote:
>Hi,
>
>I have experimented a little bit, and I think I have found a way how to
>embed Lisp-1 into Lisp-2, or rather: how to get the effects that make
>Lisp-1 convenient in some circumstances.
>
>Instead of changing the behavior of variables or introducing unifying
>environments I have just written a macro that turns all function forms
>inside of it into equivalent funcalled expressions.
>
>The gist of the idea is that you need to declare in the macro which
>functions you want to "import" into this reduced namespace. Here is an
>example:
>
>(with-lisp-1 (print list)
> (let ((f (lambda (x) (+ x x))))
> (print (list (f 1) (f 2) (f 3)))))
>
>The first parameter to with-lisp-1 names the functions that get bound to
>variables of the same names. Here is what the above example is (roughly)
>macroexpanded into:
>
>(let ((print 'print) (list 'list))
> (let ((f (lambda (x) (+ x x))))
> (funcall print (funcall list (funcall f 1) (funcall f 2) (funcall f
>3)))))
I like the idea, but can you not avoid having to
specify the global function names that should be
imported, which could so quickly turn into a pain?
I.e. insert the funcall for anything that with-lisp-1
can determine was introduced by let within its argument
text, but not for the others. Or something like
that.
(This means that local functions introduced by a let
outside the with-lisp-1 are not recognized as
locals, but that's an acceptable cost IMHO.)
--
dorai!sitaram!!core!verizon!com ······@/ | s/!/./g
In article <············@news.gte.com>,
····@goldshoe.gte.com (Dorai Sitaram) wrote:
> I like the idea, but can you not avoid having to
> specify the global function names that should be
> imported, which could so quickly turn into a pain?
> I.e. insert the funcall for anything that with-lisp-1
> can determine was introduced by let within its argument
> text, but not for the others. Or something like
> that.
>
> (This means that local functions introduced by a let
> outside the with-lisp-1 are not recognized as
> locals, but that's an acceptable cost IMHO.)
Hmm, if you really think that this is an acceptable solution it would
actually make the implementation easier (or so I guess at the moment).
Have you thought about this carefully, or this is a first guess of
yours? (Since I know that you're a Schemer I guess you are probably
right.)
Pascal
From: Dorai Sitaram
Subject: Re: Lisp-1 vs. Lisp-2: A possible solution
Date:
Message-ID: <bbd882$fku$1@news.gte.com>
In article <······························@news.netcologne.de>,
Pascal Costanza <········@web.de> wrote:
>In article <············@news.gte.com>,
> ····@goldshoe.gte.com (Dorai Sitaram) wrote:
>
>> I like the idea, but can you not avoid having to
>> specify the global function names that should be
>> imported, which could so quickly turn into a pain?
>> I.e. insert the funcall for anything that with-lisp-1
>> can determine was introduced by let within its argument
>> text, but not for the others. Or something like
>> that.
>>
>> (This means that local functions introduced by a let
>> outside the with-lisp-1 are not recognized as
>> locals, but that's an acceptable cost IMHO.)
>
>Hmm, if you really think that this is an acceptable solution it would
>actually make the implementation easier (or so I guess at the moment).
>
>Have you thought about this carefully, or this is a first guess of
>yours? (Since I know that you're a Schemer I guess you are probably
>right.)
The goal appears to me to be to avoid having to put
funcalls in pockets of code that are "mathematical"
(like the SICM example). And if focusing on that goal
will help reduce the amount of specification from the
programmer, that's a great practical win.
If the pocket contains calls to local function names
from enclosing contexts, the programmer has the option
of (a) using funcalls as before (but fewer of them), or
(b) he can just move the with-lisp-1 outward to
include those locals. It's a purely lexical decision
and easy to make.
--
dorai!sitaram!!core!verizon!com ······@/ | s/!/./g
In article <············@news.gte.com>,
····@goldshoe.gte.com (Dorai Sitaram) wrote:
> >Have you thought about this carefully, or this is a first guess of
> >yours? (Since I know that you're a Schemer I guess you are probably
> >right.)
>
> The goal appears to me to be to avoid having to put
> funcalls in pockets of code that are "mathematical"
> (like the SICM example). And if focusing on that goal
> will help reduce the amount of specification from the
> programmer, that's a great practical win.
>
> If the pocket contains calls to local function names
> from enclosing contexts, the programmer has the option
> of (a) using funcalls as before (but fewer of them), or
> (b) he can just move the with-lisp-1 outward to
> include those locals. It's a purely lexical decision
> and easy to make.
Yes, this sounds very reasonable. I will try to incorporate this idea.
Pascal
Pascal Costanza wrote:
> The gist of the idea is that you need to declare in the macro which
> functions you want to "import" into this reduced namespace. Here is an
> example:
>
> (with-lisp-1 (print list)
> (let ((f (lambda (x) (+ x x))))
> (print (list (f 1) (f 2) (f 3)))))
>
> The first parameter to with-lisp-1 names the functions that get bound to
> variables of the same names. Here is what the above example is (roughly)
> macroexpanded into:
>
> (let ((print 'print) (list 'list))
> (let ((f (lambda (x) (+ x x))))
> (funcall print (funcall list (funcall f 1) (funcall f 2) (funcall f
> 3)))))
>
> Another idea here is that if you defun a function inside of a
> with-lisp-1 macro you need to import the respective symbol beforehand.
> (That's why the variables get bound to quoted names, not to sharp-quoted
> ones.)
It's 1.20am here, so the following may be nuts, but:
how about making it expand instead into
(symbol-macrolet ((print #'print) (list #'list))
(let ((f (lambda (x) (+ x x))))
(funcall print (funcall list (funcall f 1)
(funcall f 2)
(funcall f 3)))))
That would seem to result in more efficient code, but
maybe there's some obvious reason why it doesn't work.
--
Gareth McCaughan ················@pobox.com
.sig under construc
In article <·······························@g.local>,
Gareth McCaughan <················@pobox.com> wrote:
> It's 1.20am here, so the following may be nuts, but:
> how about making it expand instead into
>
> (symbol-macrolet ((print #'print) (list #'list))
> (let ((f (lambda (x) (+ x x))))
> (funcall print (funcall list (funcall f 1)
> (funcall f 2)
> (funcall f 3)))))
>
> That would seem to result in more efficient code, but
> maybe there's some obvious reason why it doesn't work.
>
That's a cute idea - I haven't thought about this before.
Thanks for that!
Pascal