In the TXR Lisp dialect, the problem is approached from the other end. Starting with Lisp-2 dialect as a basis, can we robe ourselves with some of the expressivity advantages of a Lisp-1 dialect, like eliminating the (function ...)
, #'
and funcall
noise from programs that make extensive use of higher order functions?
The design is centered around a special operator called dwim
, which stands for either "Do What I Mean" or "Dispatch, in a Way that is Intelligent and Meaningful".
Invocations of the dwim
operator are sugared over using square brackets, which are called "DWIM Brackets"
The dwim
operator isn't just a macro over Lisp-2; it actually changes the name lookup rules. When we have
(dwim a b c (d e f) g)
Or equivalently:
[a b c (d e f) g]
all of the argument forms which are symbolic (a
, b
, c
and g
) are resolved using a special rule which conflates together the function and variable namespaces. This is built into the heart of the language. The operator has direct access to the environment to make this possible.
The special treatment does not recurse into (d e f)
, which is an ordinary Lisp-2 form. You have to put the DWIM Brackets on that if you want the semantics.
Also, the dwim
operator is properly handled by macro expansion. For instance, given:
(symacrolet ((localfun whatever))
(flet ((localfun () ...)))
[a b c localfun] ;; refers to the flet as a function object!
(a b c localfun))) ;; refers to the symbol macro!
The macro expander knows about dwim
and its semantics, and so it considers the possibility that localfun
refers to the function and variable namespace. The closest lexical binding in either namespace is the flet
and so the symbol macro expansion is suppressed (shadowed).
The dwim
semantics is implicitly used in the partial evaluating op
macro and its "cousins" derived from it.
Range Extraction task from Rosetta Code:
(defun range-extract (numbers)
`@{(mapcar [iff [callf > length (ret 2)]
(ret `@[@1 0]-@[@1 -1]`)
(ret `@{@1 ","}`)]
(mapcar (op mapcar car)
(split [window-map 1 :reflect
(op list @2 (- @2 @1))
(sort (uniq numbers))]
(op where [chain second (op < 1)])))) ","}`)
Y Combinator:
;; The Y combinator:
(defun y (f)
[(op @1 @1)
(op f (op [@@1 @@1]))])
;; The Y-combinator-based factorial:
(defun fac (f)
(do if (zerop @1)
1
(* @1 [f (- @1 1)])))
;; Test:
(format t "~s\n" [[y fac] 4])
Moreover, various useful things are function callable in TXR Lisp. For instance, every sequence (list, vector or character string) is regarded as a function which maps numeric indices to elements. Thus we can do:
(mapcar "abc" #(2 1 0)) -> #(#\c #\b #\a)
The accepted answer describes a Racket mechanism for treating structures as functions. TXR has this in the form of lambda
methods. This is demonstrated in the "OOP-Based" solution to the Accumulator Factory task in Rosetta:
(defstruct (accum count) nil
(count 0))
(defmeth accum lambda (self : (delta 1))
(inc self.count delta))
We can instantiate a (new (accum 9))
which will produce the values 10
, 11
, 12
, ... when invoked as a function. An optional delta argument can be supplied for an increment other than 1
:
(let ((acc (new (accum 0))))
(list [acc 5] [acc 5] [acc])) -> (5 10 11)