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

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.