On Wednesday, August 3, 2016 at 11:04:06 AM UTC-7, Steve Graham wrote:
>
> (defun find-a-r (lst &optional (acc 0))
> (cond
> ((null lst) acc)
> ((eq (car lst) 'a) (find-a-r (cdr lst) (1+ acc)))
> (t (find-a-r (cdr lst) acc))))
>
> I used 2 functions because I did not know how to initialize the accumulator
> and then use it for updating using only 1 function.
Well, you would need to initialize it in the call if it is not &optional.
The reason for not exposing that variable in the main function is that it is
an implementation detail and not really something the user (caller) of the
function should need to know about.
Especially since you could substitute an iterative solution that doesn't need
the variable in its argument list at all.
This is an important principle of encapsulation, in that you try not to put
internal details of exactly how the function works into the external interface
that other programmers or even your own code uses. That makes it easier to
change the way the function works without affecting anyone who uses it, so long
as it still computes the same answer.
> I tried a local variable and when it didn't work.
Right, because the local variable is local and thus only visible inside the
text of that function, but not in the recursive call.
> I tried the global variable, which of course also didn't work.
Well, you can get it to work, but it complicates things as Kaz so nicely
explained.
> Do you think there is a way to accomplish this without the optional
> parameter? The reason I ask is that Paul Graham had not yet covered
> it in the material preceding the exercise.
Yes, there are a couple of ways.
One is to dispense with the accumulating parameter entirely and just work with
the return values. The accumulating parameter is an optimization and not
necessary for the correctness of the function.*
(defun find-a-r (list)
(cond ((null list) 0)
((eq (car list) 'a)
(1+ (find-a-r (cdr list))))
(t (find-a-r (cdr list)))))
The other is to use a locally-defined internal function, which Paul Graham also
would not have covered yet. But all this does is create the two-argument
version of the function as an internal function and use that.
(defun find-a-r (lst)
(labels ((find-a-r-internal (lst acc)
(cond
((null lst) acc)
((eq (car lst) 'a) (find-a-r-internal (cdr lst) (1+ acc)))
(t (find-a-r-internal (cdr lst) acc)))))
(find-a-r-internal lst 0)))
I would also note that normally you would want the item you are looking for to
also be a parameter rather than being present in your code directly. Perhaps as
an additional exercise you would want to write
(defun count-items (list item)
...)
(count-items lst 'a) <==> (find-a-r lst)
======
* So long as you don't overflow the stack.
And in spite of WG's comment that the Common Lisp standard doesn't mandate
the elimination of tail recursion**, most of the actual implementations will
perform the optimization.
**
https://en.wikipedia.org/wiki/Tail_call