tech/newlisp
Contexts and namespaces
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.
; Scheme closure
(define make-adder
(lambda (n)
(lambda (x) (+ x n))))
(define add3 (make-adder 3)) => #<procedure add3>
(add3 10) => 13
; 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
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:
(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).
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
The first class nature of lambda expressions in newLISP make it possible to write self modifying code.
Impressions
It seems a very original and brave PL: dynamic scope; first class namespaces; no GC; pass by value; etc..
A problem is that it simplify the semantic of the language respect CL, but it is not so much natural to combine them for obtaining powerful things. But to be fair, the same can be saw also of CL and Scheme.