lexical-let

Pondering the philosophy behind the language
Locked
William James
Posts: 58
Joined: Sat Jun 10, 2006 5:34 am

lexical-let

Post by William James »

Before version 24.1, EMACS Lisp was dynamically scoped; now lexical scoping
is an option. When a closure was needed, it was customary to load the
CL package and use lexical-let.

It would be very nice if lexical-let were added to newLISP. Closures
could be created more easily, by a method that is more readily
understood by users of other dialects of Lisp. It would make it easier
to demonstrate the power of newLISP to potential converts.

There is a book titled "Let over Lambda" that demonstrates the
advanced use of closures; when lexical-let is added to newLISP,
converting the code in the book will be a trivial task.

The amount of code added to the newLISP distribution would be
miniscule. It's much better to have the facility bundled with the
language rather than having to include the code for the macro when
you post an example in a forum like Stackoverflow. Brevity will make a
better impression on interested programmers.

I know that closures can be created using contexts, but it seems to me
that most programmers will find it easier to use a lexical let; they
are used to doing it that way in other Lisps and in Scheme. Since it
makes programming easier and adding it to the language would be dead
simple, it would be a shame not to include it.

To get the ball rolling, here is my attempt at an implementation.
(uuid is used instead of gensym.)

Code: Select all

(context 'lexical-let)
(define-macro (lexical-let:lexical-let varval-pairs)
  (let (body (cons 'begin (args))
        alist (map (fn(x) (list (x 0) (sym (string 's (uuid))) (eval (x 1))))
                   (explode varval-pairs 2)))
    (bind (map rest alist))
    (dolist (x alist) (set-ref-all (x 0) body (x 1)))
    (eval body)))
(context MAIN)

;; Example

(define (make-fibber)
  (lexical-let (a 0  b 1)
    (fn() (swap a b) (++ b a) a)))
newLISP's popularity cannot be decreased, and may very well be
increased, by adding an implementation of this.

rickyboy
Posts: 607
Joined: Fri Apr 08, 2005 7:13 pm
Location: Front Royal, Virginia

Re: lexical-let

Post by rickyboy »

Hi William,

This is a very interesting idea to think about. I like how your implementation handles expressions under the lexical-let that reference both "lexical" and dynamic "variables". Also, it works for the "Hello, world!" of let-over-lambda, which is the account withdraw function.

Code: Select all

> (lexical-let (balance 100) (define (withdraw amt) (dec balance amt)))
> (withdraw 10)
90
> (withdraw 2)
88
However, if you code up an example that has to leverage the "environment chaining" that has to take place in lexical scope, your implementation doesn't handle this (yet).

Code: Select all

> (lexical-let (x 42) (define (g y z) (list x y z)) (define (f x) (g x 3)))
> (f 1)
(1 1 3)
But the answer is supposed to be (42 1 3):

Code: Select all

$ clisp
[1]> (let ((x 42)) (defun g (y z) (list x y z)) (defun f (x) (g x 3)))
F
[2]> (f 1)
(42 1 3)
In this example, your implementation doesn't yet distinguish between x in the body of g and x in the body of f. The dolist in the definition of lexical-let will replace every symbol x, under the lexical-let, with the same gensym. This will have to be fixed of course.

So, go to it! I am cheering for you. It looks like a very fun project. Thanks for sharing and please continue to keep us posted.
(λx. x x) (λx. x x)

Lutz
Posts: 5289
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Re: lexical-let

Post by Lutz »

I agree with Rick, that William's lexical-let macro is well done and with clever usage of the uuid function to generate unique symbols.

But if the goal is to create functions with static variables, that can be achieved simpler and more efficient using namespaces:

Code: Select all

> (define (withdraw:withdraw x) (dec withdraw:balance x)) ; returns current balance
(lambda (x) (dec withdraw:balance x))

> (withdraw -100) ; initial credit
100
> (withdraw 10)
90
> (withdraw 2)
88
>

Locked