Kyle
Kyle

Reputation: 793

LISP Cannot take CAR of T

I am trying to evaluate each atom of a list and see if it's equal to the number provided and remove if its not but I am running into a slight problem.

I wrote the following code:

(defun equal1(V L)
     (cond((= (length L) 0))
          (T (cond( (not(= V (car(equal1 V (cdr L))))) (cdr L) )))
     )
)

(equal1 5 '(1 2 3 4 5))

I obtain the following error

Error: Cannot take CAR of T.

If I add (write "hello") for the action if true, the following error is obtained:

Error: Cannot take CAR of "hello".

I'm still quite new to LISP and was wondering what exactly is going on and how could I fix this so I could evaluate each atom properly and remove it if its not, thus the cdr L for the action.

Upvotes: 0

Views: 1327

Answers (2)

Sylwester
Sylwester

Reputation: 48765

car and cdr are accessors of objects of type cons. Since t and "hello" are not cons you get an error message.

To fix it you need to know what types your function returns and not car unless you know that it's a cons

EDIT

First off ident and clean up the code.. The nested cond are uneccesary since cond is a if-elseif-else structure by default:

(defun remove-number (number list)
  (cond ((= (length list) 0) 
         t)
        ((not (= number (car (remove-number number (cdr list))))) 
         (cdr list))))
        (t 
         nil)))

I want you to notice I've added the default behaviour of returning t when a consequent is not given as we know = returns either t or nil so it returns t when the length is 0 in this case.

I've added the default case where none of the two previous predicates were truthy and it defaults to returning nil.

I've named it according to the functions used. = can only be used for numeric arguments and thus this will never work on symbols, strings, etc. You need to use equal if you were after values that look the same.

Looking at this now we can see that the functions return value is not very easy to reason about. We know that t, nil and list or any part of the tail of list are possible and thus doing car might not work or in the case of (car nil) it may not produce a number.

A better approach to doing this would be:

  1. check if the list is empty, then return nil
  2. check if the first element has the same numeric value as number, then recurse with rest of the list (skipping the element)
  3. default case should make cons a list with the first element and the result fo the recursion with the rest of the list.

The code would look something like this:

(defun remove-number (number list)
  (cond ((endp list) '())
        ((= (car list) number) (remove-number ...))
        (t (cons ...))))

Upvotes: 2

David Hodge
David Hodge

Reputation: 269

There are a couple of things you could do to improve this function.

Firstly, let's indent it properly

(defun equal1 (V L)
  (cond
    ((= (length L) 0))
    (T (cond
         ((not (= V (car (equal1 V (cdr L))))) (cdr L))))))

Rather than saying (= (length l) 0), you can use (zerop (length l)). A minor sylistic point. Worse is that branch returns no value. If the list L is empty what should we return?

The issue with the function is in the T branch of the first cond.

What we want to do is

  1. remove any list item that is the same value as V
  2. keep any item that is not = to V

The function should return a list.

The expression

(cond 
  ((not (= V (car (equal1 V (cdr L))))) (cdr L)))

is trying (I think) to deal with both conditions 1 and 2. However it's clearly not working.

We have to recall that items are in a list and the result of the equal function needs to be a list. In the expression above the result of the function will be a boolean and hence the result of the function call will be boolean.

The function needs to step along each element of the list and when it sees a matching value, skip it, otherwise use the cons function to build the filtered output list.

Here is a skeleton to help you out. Notice we don't need the embedded cond and just have 3 conditions to deal with - list empty, filter a value out, or continue to build the list.

(defun equal-2 (v l)
  (cond
    ((zerop (length L))  nil)
    ((= v (car l))  <something goes here>)      ;skip or filter the value
    (t (cons (car l) <something goes here>)))) ;build the output list

Of course, this being Common Lisp, there is a built-in function that does this. You can look into remove-if...

Upvotes: 1

Related Questions