durandaltheta
durandaltheta

Reputation: 255

Unusual Scheme `let` binding, what is `f`?

In "The Scheme Programming Language 4th Edition" section 3.3 Continuations the following example is given:

(define product
  (lambda (ls)
    (call/cc
      (lambda (break)
        (let f ([ls ls])
          (cond
            [(null? ls) 1]
            [(= (car ls) 0) (break 0)]
            [else (* (car ls) (f (cdr ls)))]))))))

I can confirm it works in chezscheme as written:

> (product '(1 2 3 4 5))
120

What is 'f' in the above let? Why is the given ls being assigned to itself? It doesn't seem to match what I understand about (let ...) as described in 4.4 local binding:

syntax: (let ((var expr) ...) body1 body2 ...)

If 'f' is being defined here I would expect it inside parenthesis/square brackets:

(let ([f some-value]) ...)

Upvotes: 1

Views: 180

Answers (3)

Sylwester
Sylwester

Reputation: 48745

Think of this procedure:

(define (sum lst)
  (define (helper lst acc)
    (if (null? lst)
        acc
        (helper (cdr lst) 
                (+ (car lst) acc))))
  (helper lst 0))

(sum '(1 2 3)) ; ==> 6

We can use named let instead of defining a local procedure and then use it like this:

(define (sum lst-arg)
  (let helper ((lst lst-arg) (acc 0))
    (if (null? lst)
        acc
        (helper (cdr lst) 
                (+ (car lst) acc)))))

Those are the exact same code with the exception of some duplicate naming situations. lst-arg can have the same name lst and it is never the same as lst inside the let.

Named let is easy to grasp. call/ccusually takes some maturing. I didn't get call/cc before I started creating my own implementations.

Upvotes: 0

user5920214
user5920214

Reputation:

This is 'named let', and it's a syntactic convenience.

(let f ([x y] ...)
  ...
  (f ...)
  ...)

is more-or-less equivalent to

(letrec ([f (λ (x ...)
              ...
              (f ...)
              ...)])
  (f y ...))

or, in suitable contexts, to a local define followed by a call:

(define (outer ...)
  (let inner ([x y] ...)
    ...
    (inner ...)
    ...))

is more-or-less equivalent to

(define (outer ...)
  (define (inner x ...)
    ...
    (inner ...)
    ...)
  (inner y ...))

The nice thing about named let is that it puts the definition and the initial call of the local function in the same place.

Cavemen like me who use CL sometimes use macros like binding, below, to implement this (note this is not production code: all its error messages are obscure jokes):

(defmacro binding (name/bindings &body bindings/decls/forms)
  ;; let / named let
  (typecase name/bindings
    (list
     `(let ,name/bindings ,@bindings/decls/forms))
    (symbol
     (unless (not (null bindings/decls/forms))
       (error "a syntax"))
     (destructuring-bind (bindings . decls/forms) bindings/decls/forms
       (unless (listp bindings)
         (error "another syntax"))
       (unless (listp decls/forms)
         (error "yet another syntax"))
       (multiple-value-bind (args inits)
           (loop for binding in bindings
                 do (unless (and (listp binding)
                                 (= (length binding) 2)
                                 (symbolp (first binding)))
                      (error "a more subtle syntax"))
                 collect (first binding) into args
                 collect (second binding) into inits
                 finally (return (values args inits)))
         `(labels ((,name/bindings ,args
                     ,@decls/forms))
            (,name/bindings ,@inits)))))
    (t
     (error "yet a different syntax"))))

Upvotes: 2

Fredrik Arnerup
Fredrik Arnerup

Reputation: 63

f is bound to a procedure that has the body of let as a body and ls as a parameter.

http://www.r6rs.org/final/html/r6rs/r6rs-Z-H-14.html#node_sec_11.16

Upvotes: 1

Related Questions