From: Pascal Costanza
Subject: Lisp-1 vs. Lisp-2: A possible solution
Date: 
Message-ID: <costanza-E1D193.15111801062003@news.netcologne.de>
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)))

From: Paul F. Dietz
Subject: Re: Lisp-1 vs. Lisp-2: A possible solution
Date: 
Message-ID: <tKOcnWLLte09m0ejXTWcrg@dls.net>
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
From: Erann Gat
Subject: Re: Lisp-1 vs. Lisp-2: A possible solution
Date: 
Message-ID: <gat-0106030934020001@192.168.1.51>
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.
From: Pascal Costanza
Subject: Re: Lisp-1 vs. Lisp-2: A possible solution
Date: 
Message-ID: <costanza-A1D046.19131801062003@news.netcologne.de>
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
From: Erann Gat
Subject: Re: Lisp-1 vs. Lisp-2: A possible solution
Date: 
Message-ID: <gat-0106031135330001@192.168.1.51>
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.
From: Pascal Costanza
Subject: Re: Lisp-1 vs. Lisp-2: A possible solution
Date: 
Message-ID: <costanza-EECFEF.02533002062003@news.netcologne.de>
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
From: Pascal Costanza
Subject: Re: Lisp-1 vs. Lisp-2: A possible solution
Date: 
Message-ID: <costanza-8076BE.17103601062003@news.netcologne.de>
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
From: Pascal Costanza
Subject: Re: Lisp-1 vs. Lisp-2: A possible solution
Date: 
Message-ID: <costanza-94632A.19140801062003@news.netcologne.de>
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
From: Gareth McCaughan
Subject: Re: Lisp-1 vs. Lisp-2: A possible solution
Date: 
Message-ID: <slrnbdl692.1i9.Gareth.McCaughan@g.local>
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
From: Pascal Costanza
Subject: Re: Lisp-1 vs. Lisp-2: A possible solution
Date: 
Message-ID: <costanza-A6A6BD.03025602062003@news.netcologne.de>
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