Sunday, July 24, 2022

Named Lambda and Named Let

Suppose you want to map the "add three" function over a list. You don't need to define a name for the function, you can just use a lambda expression: (lambda (n) (+ n 3)). This creates an anonymous "add three" function.

Now suppose you want to map the "factorial" function over a list. You start with (lambda (x) (if (zerop x) 1 ... , but how can you recursively call an anonymous function? We need the function to have a local name within its own body. One option is to use the Y operator:

* (map 'list (Y (lambda (fact)
                  (lambda (x)
                    (if (zerop x)
                        1
                        (* x (funcall fact (1- x)))))))
       '(3 5 7))
(6 120 5040)
but another popular option is to provide a new special form
* (map 'list (named-lambda fact (x)
               (if (zerop x)
                   1
                   (* x (fact (1- x)))))
     '(3 5 7))
(6 120 5040)
The name fact is bound to the lambda expression only within the body of the lambda expression. You don't need to defun a factorial procedure, you can just use a named-lambda.

A little puzzle for you: write named-lambda. My answer below.

Just as a let is syntactic sugar for a lambda application, a named-let is syntactic sugar for a named-lambda application. The name is bound to the lambda expression that performs the variable binding, so you can use that name to make a recursive call. In effect, you can re-invoke the named-let expression with a fresh set of values.

Scheme hackers will be familiar with named-let, but it isn't usual in Common Lisp. It's an easy transformation:

(named-let recur ((x '(a list))
                  (y 22))    
   (body)) =>

(funcall (named-lambda recur (x y) (body)) '(a list) 22)
named-let is the bee's knees for ad hoc iteration. The iteration variables are bound to their initial values by the named-let, and the body can initiate the next iteration by tail-calling the named-let with the updated values for the iteration variables. Since there is no constraint on where or when you iterate, or how you update the iteration variables, this allows very general iteration.

I have seen a tendency for Scheme hackers to overdo it with named lets. You don't need a named-let when map will do. It's usually better to express an iteration through a higher order function than to write yet another ad hoc loop in your code. But named-let is a useful and powerful tool when the simpler methods aren't cutting it.

Here's what I came up with for named-lambda:

(defmacro named-lambda (name (&rest arglist) &body body)
  `(labels ((,name ,arglist ,@body))
     (function ,name)))

5 comments:

  1. Isn't named-lambda very similar to Paul Graham's alambda?

    ReplyDelete
  2. SRFI 31 provides the rec macro as follows:

    (define-syntax rec
      (syntax-rules ()
        ((rec (NAME . VARIABLES) . BODY)
         (letrec ( (NAME (lambda VARIABLES . BODY)) ) NAME))
        ((rec NAME EXPRESSION)
         (letrec ( (NAME EXPRESSION) ) NAME))))

    ReplyDelete
  3. In s7, define returns its value so (map (define (fact n) (if (< n 2) 1 (* n (fact (- n 1))))) '(3 5)) -> '(6 120)
    You could wrap the define in a let as rec does to keep the name out of the calling environment.

    ReplyDelete
  4. At least in Common Lisp, your named-lambda definition doesn't match usage.

    As defined, you'd have to say (named-lambda (fact x) ...), but you used (named-lambda fact (x) ...).

    The CL defmacro should go (defmacro named-lambda (name (&rest arglist) &body body) ...)

    ReplyDelete
  5. Anonymous pointed out that I posted the wrong version. I updated it.

    ReplyDelete