progner
progner

Reputation: 139

Is there a way to use iteration in Common Lisp and avoid side-effects at the same time?

I've written two versions of a lisp function. The main difference between the two is that one is done with recursion, while the other is done with iteration.

Here's the recursive version (no side effects!):

(defun simple-check (counter list)
  "This function takes two arguments: 
  the number 0 and a list of atoms. 
  It returns the number of times the 
  atom 'a' appears in that list."
  (if (null list)
      counter
      (if (equal (car list) 'a)
          (simple-check (+ counter 1) (cdr list))
          (simple-check counter (cdr list)))))

Here's the iterative version (with side effects):

(defun a-check (counter list)
  "This function takes two arguments: 
  the number 0 and a list of atoms. 
  It returns the number of times the 
  atom 'a' appears in that list."
  (dolist (item list)
    (if (equal item 'a)
        (setf counter (+ counter 1))
        (setf counter (+ counter 0))))
  counter)

As far as I know, they both work. But I'd really like to avoid side-effects in the iterative version. Two questions I'd like answered:

  1. Is it possible to avoid side effects and keep iteration?
  2. Assuming the answer to #1 is a yes, what are the best ways to do so?

Upvotes: 1

Views: 321

Answers (4)

Xero Smith
Xero Smith

Reputation: 2076

Passing counter to the recursive procedure was a means to enable a tail recursive definition. This is unnecessary for the iterative definition. As others have pointed out, there are several language constructs which solve the stated problem elegantly.

I assume you are interested in this in a more general sense such as when you cannot find a language feature that solves a problem directly. In general, one can maintain a functional interface by keeping the mutation private as below:

(defun simple-check (list)                                                                    
  "return the number of times the symbol `a` appears in `list`"                                 
  (let ((times 0))                                                                            
    (dolist (elem list times)                                                                 
      (when (equal elem 'a)                                                                   
        (incf times)))))

Upvotes: 1

Ehvince
Ehvince

Reputation: 18375

You can also iterate with map, mapcar and friends.

https://lispcookbook.github.io/cl-cookbook/iteration.html

I also suggest a look at remove-if[-not] and other reduce and apply:

(length (remove-if-not (lambda (x) (equal :a x)) '(:a :b :a)))  ;; 2

Upvotes: 3

coredump
coredump

Reputation: 38809

For completeness, note that Common Lisp has a built-in COUNT:

(count 'a list)

Upvotes: 4

Svante
Svante

Reputation: 51501

In some ways, the difference between side-effect or no side-effect is a bit blurred. Take the following loop version (ignoring that loop also has better ways):

(loop :for x :in list
      :for counter := (if (eq x 'a) (1+ counter) counter)
      :finally (return counter))

Is counter set at each step, or is it rebound? I. e., is an existing variable modified (like in setf), or is a new variable binding created (as in a recursion)?

This do version is very much like the recursive version:

(do ((list args (rest list))
     (counter 0 (+ counter (if (eq (first list) 'a) 1 0))))
    ((endp list) counter))

Same question as above.

Now the “obvious” loop version:

(loop :for x :in list
      :count (eq x 'a))

There isn't even an explicit variable for the counter. Are there side-effects?

Internally, of course there are effects: environments are created, bindings established, and, especially if there is tail call optimization, even in the recursive version destroyed/replaced at each step.

I see as side effects only effects that affect things outside of some defined scope. Of course, things appear more elegant if you can also on the level of your internal definition avoid the explicit setting of things, and instead use some more declarative expression.

Upvotes: 3

Related Questions