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)
Lists in Lisp (Scheme, Common Lisp, etc.) are built out of cons cells and a special (the empty list). That, whenever you have something that is a list, it is either:
Because there are two types of lists, recursive algorithms on lists usually answer just two questions:
For length, this means:
The type of solution presented in The Little Schemer first develops a solution that answers the first case, which means that it works for lists of length ≤0. As soon as you have a solution that handles both cases (correctly), you have a solution that works for lists of any length. There's no real incremental solution that would extend the function to work for lists of length ≤2.
You could, I suppose, do something like:
That would work for lists of length 0 and lists of length 1, and it would be incorrect for all other lists, but it would return 1 for all other lists. Unless you change the structure of the recursion, I think that's really the best you can do. If you're willing to change the structure of the recursion, you could do:
but you really shouldn't take this approach, because now you're processing lists in three different cases instead of two, which is (almost) never what you want to do.
A translation to Python
The same principle works in Python, or any language that supports higher-order functions. It's more idiomatic in Python to locally define the function and then call it, rather than calling a lambda expression directly, so this may be more readable for you:
TL;DR:
(mk-length A)
(inside thecond
form) calculates thelength
function that works for lists of length 0 and will use(A A)
to calculate thelength
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, replacingwith
works.
(NB:
(mk-length eternity)
itself calculateslength≤0
, but the overall function becomeslength≤1
; this is what all the furtherlength≤i
comments refer to.)Looking closely at
we can see that the result of
(mk-length ...)
at;(2)
is used to process(cdr l)
, while theargument
tomk-length
at;(2)
will replacemk-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) (cffffdr l))
naturally fails (for lengths 3 and up).Thus to process the lists of up to three elements,
can be used:
As you correctly surmised, this is the interim step on the way to using
which turns, on the processing of the next element of the list into
and thus works for every list, whatever its length is.
And by eta-conversion this is just
(mk-length mk-length)
.