In the book The little schemer, we find this function that only supports lists with length smaller than or equal to 1:
(((lambda (mk-length) ; A.
(mk-length mk-length))
(lambda (mk-length)
(lambda (l)
(cond
((null? l ) 0)
(else (add1 ((mk-length eternity ) (cdr l))))))))
'(1))
I want to study step by step, and want to write the similar function that supports only lists of length smaller than or equal to 2.
Please, don't answer this question by offering code like:
(((lambda (mk-length) ; B.
(mk-length mk-length))
(lambda (mk-length)
(lambda (l)
(cond
((null? l) 0 )
(else (add1((mk-length mk-length) (cdr l))))))))
'(a b c d))
because this function supports any length.
And I already know how to write function like this:
(((lambda (mk-length) ; C.
(mk-length
(mk-length (mk-length eternity))))
(lambda (length)
(lambda (l)
(cond
((null? l) 0)
(else (add1 (length (cdr l))))))))
'(1 2)) ;;
to achieve my goal. But this code is more than one step away from the first snippet.
Maybe, I should not change:
(lambda (mk-length) ; D.
(mk-length mk-length)
TL;DR: (mk-length A)
(inside the cond
form) calculates the length
function that works for lists of length 0 and will use (A A)
to calculate the length
function that will be used to work on the tail (i.e. the result of (cdr ...)
) of the argument list.
Your first code snippet (;A.
) works for lists of length 0 and 1 only. To make it work for 2 also, replacing
(mk-length eternity) ; length≤1
with
(mk-length ; (2) ; length≤2
(lambda (x) (mk-length eternity)))
works.
(NB: (mk-length eternity)
itself calculates length≤0
, but the overall function becomes length≤1
; this is what all the further length≤i
comments refer to.)
Looking closely at
(((lambda (mk-length)
(mk-length mk-length))
(lambda (mk-length) ; (1)
(lambda (l)
(cond
((null? l ) 0)
(else (add1 ((mk-length ; (2) ; length≤2
(lambda (x) (mk-length eternity)) )
(cdr l))))))))
'(1 2))
we can see that the result of (mk-length ...)
at ;(2)
is used to process (cdr l)
, while the argument
to mk-length
at ;(2)
will replace mk-length
in that call when processing the (cddr l)
.
If (mk-length eternity)
is used (as in your first code), (cdr l)
gets processed OK but ((eternity eternity) (cddr l))
naturally fails.
If (mk-length (lambda (x) (mk-length eternity)))
is used, (cdr l)
gets processed OK and then ((lambda (x) (mk-length eternity)) (lambda (x) (mk-length eternity))) = (mk-length eternity)
is used to process (cddr l)
which is also OK (so, length 2 is handled correctly), and then ((eternity eternity) (cdddr l))
naturally fails (for lengths 3 and up).
Thus to process the lists of up to three elements,
((mk-length ; (2) ; length≤3
(lambda (x) (mk-length
(lambda (x) (mk-length eternity)))) )
can be used:
(define (eternity x) (- 1)) ; to get an error instead of looping
(((lambda (mk-length)
(mk-length mk-length))
(lambda (mk-length)
(lambda (l)
(cond
((null? l ) 0)
(else (add1 ((mk-length ; (2) ; length≤3
(lambda (x) (mk-length
(lambda (x) (mk-length eternity)))) )
(cdr l))))))))
'(1 2 3)) ; => 3
; ...........
; '(1 2 3 4)) ; => **error**
As you correctly surmised, this is the interim step on the way to using
(mk-length (lambda (x) (mk-length x))) ; (2) ; length≤∞
which turns, on the processing of the next element of the list into
((lambda (x) (mk-length x)) (lambda (x) (mk-length x)))
=
(mk-length (lambda (x) (mk-length x)))
and thus works for every list, whatever its length is.
And by eta-conversion this is just (mk-length mk-length)
.