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)
.
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:
- The empty list ()
- A cons cell (also called a pair) where the car of the pair is the first element of the list and the cdr of the pair is the rest of the list.
Because there are two types of lists, recursive algorithms on lists usually answer just two questions:
- What is the result for the empty list?
- What is the result for rest of the list and how is that turned into the result for the whole list?
For length, this means:
- The length of the empty list is 0.
- When the length of the rest of the list is n, the length of the whole list is n + 1.
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:
(((lambda (mk-length)
(mk-length mk-length))
(lambda (mk-length)
(lambda (l)
(cond
((null? l ) 0)
(else 1)))))
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:
(((lambda (mk-length)
(mk-length mk-length))
(lambda (mk-length)
(lambda (l)
(cond
((null? l) 0)
((null? (cdr l)) 1)
(else (add1 ((mk-length eternity ) (cdr l))))))))
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
Can this thinking rewrite by python or something process language? It is still hard to image a auto created fun in recurse.
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:
def length(l):
def driver(recurse):
"Returns the result of calling `recurse` with itself."
return recurse(recurse);
def zeroForEmptyListOrElseOnePlusRecurse(recurse):
"""Returns a function that returns 0 if its input is the
empty list, or else returns one plus whatever calling
`recurse(recurse)` with the tail of the list returns."""
def length(l):
"""Returns 0 if l is the empty list, and one plus
the results of `(recurse(recurse))(l)` otherwise."""
if l == []:
return 0;
else:
_, *rest = l
return 1+(recurse(recurse))(rest)
return length
return (driver(zeroForEmptyListOrElseOnePlusRecurse))(l)
print(length([1,2,3])) # 3
print(length([1,2])) # 2
print(length([1])) # 1
print(length([])) # 0
来源:https://stackoverflow.com/questions/29435915/little-schemer-write-function-that-only-supports-lists-of-length-%e2%89%a4-2