lexical-let

Started by William James, October 06, 2013, 09:44:14 PM

Previous topic - Next topic

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.)


(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

#1
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.


> (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).


> (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):


$ 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

#2
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:



> (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
>