Closures, Contexts and Stateful Functions
Scheme uses closures to write function factories, functions with state and software objects. newLISP uses variable expansion and namespaces called contexts to do the same.
newLISP namespaces are always open to inspection. They are first class objects which can be copied and passed as parameters to built-in newLISP primitives or user-defined lambda functions.
A newLISP context can contain several functions at the same time. This is used to build software modules in newLISP.
Like a Scheme closure a newLISP context is a lexically closed space. In newLISP inside that namespace scoping is dynamic. newLISP allows mixing lexical and dynamic scoping in a flexible way.
Function factories
The first is a simple example of a function factory. The function makes an adder function for a specific number to add. While Scheme uses a function closure to capture the number in a static variable, newLISP uses an expand function to create a specific lambda function containing the number as a constant:
; Scheme closure
(define make-adder
(lambda (n)
(lambda (x) (+ x n))))
(define add3 (make-adder 3)) => #<procedure add3>
(add3 10) => 13
newLISP uses either expand or letex to make n part of the lambda expression as a constant, or it uses curry:
; newLISP using expand
(define (make-adder n)
(expand (lambda (x) (+ x n)) 'n))
(define add3 (make-adder 3))
(add3 10) => 13
; newLISP using letex
(define (make-adder n)
(letex (c n) (lambda (x) (+ x c))))
; or letex on same symbol
(define (make-adder n)
(letex (n n) (lambda (x) (+ x n))))
(define add3 (make-adder 3))
(add3 10) => 13
; newLISP using curry
(define add3 (curry + 3))
(add3 10) => 13
In either case we create a lambda expression with the 3 contained as a constant.
Functions with memory
The next example uses a closure to write a generator function. It produces a different result each time it is called and remembers an internal state:
; Scheme generator
(define gen
(let ((acc 0))
(lambda () (set! acc (+ acc 1)))))
(gen) => 1
(gen) => 2
In newLISP we create local state variables using a name-space context:
; newLISP generator
(define (gen:gen)
(setq gen:sum
(if gen:sum (inc gen:sum) 1)))
; this could be written even shorter, because
; 'inc' treats nil as zero
(define (gen:gen)
(inc gen:sum))
(gen) => 1
(gen) => 2
When writing gen:gen, a context called gen is created. gen is a lexical name-space containing its own symbols used as variables and functions. In this case the name-space gen has the variables gen and sum.
The first symbol gen has the same name as the parent context gen. This type of symbol is called a default functor in newLISP.
When using a context name in place of a function name, then newLISP assumes the default functor. We can call our generator function using (gen). It is not necessary to call the function using (gen:gen), (gen) will default to (gen:gen).
Watch the movie here.
Create a function def-static to automate the process of defining lexically scoped functions.
Introspection
In newLISP the inner state of a function can always be queried. In Scheme the state of a closure is hidden and not open to introspection without extra code:
; in Scheme states are hidden
add3 #<procedure add3>
gen => #<procedure gen>
; in newLISP states are visible
add3 => (lambda (x) (+ x 3))
gen:sum => 2
gen:gen => (lambda () (inc gen:sum))
In Scheme lambda closure is hidden from inspection, once they are evaluated and assigned.
Functions in newLISP are first class lists
(define (double x) (+ x x)))
(setf (nth 1 double) '(mul 2 x))
double => (lambda (x) (mul 2 x))
The first class nature of lambda expressions in newLISP make it possible to write self modifying code.
Stateful functions using in-place modification
;; sum accumulator
(define (sum (x 0)) (inc 0 x))
(sum 1) ;=> 1
(sum 2) ;=> 3
sum ;=> (lambda ((x 0)) (inc 3 x))
;; self incremeneter
(define (incre) (inc 0))
(incre) ;=> 1
(incre) ;=> 2
(incre) ;=> 3
incre ;=> (lambda () (inc 3)
;; make stream function with expansion closure
(define (make-stream lst)
(letex (stream lst)
(lambda () (pop 'stream))))
(set 'lst '(a b c d e f g h))
(define mystream (make-stream lst))
(mystream) ;=> a
(mystream) ;=> b
(mystream) ;=> c
(set 'str "abcddefgh")
(define mystream (make-stream str))
(mystream) ;=> "a"
(mystream) ;=> "c"
Another interesting self modifying pattern was shown by Kazimir Majorinc from http://kazimirmajorinc.com:
The pattern called crawler-tractor will run forever without using iteration or recursion. New code to be executed is copied from old code and appended to the end of the function. Old executed code is popped of from the beginning of the function. Se also here.
(define (f)
(begin
(println (inc cnt))
(push (last f) f -1)
(if (> (length f) 3) (pop f 1))))
The ability to write self modifying functions is unique to newLISP.