Márton Kardos
Márton Kardos

Reputation: 93

Clojure : java.lang.Character cannot be cast to clojure.lang.IFn

I just wanted to write a simple little guessing game in Clojure, and I got this error. I can't see where is a Character treated like a function in my, as the resulted input also predicts that there should not be such problem. Here's the code:

(ns clojure.examples.hello
  (:gen-class))

(ns clojure-noob.core)

(defn takeFst [x n]
    (if (= n 0) () (cons (first x) (takeFst (rest x) (- n 1))))
)

(defn createSeq [elem n]
    (if (= n 0) () (cons elem (createSeq elem (- n 1))))
)

(defn fnEquals? [n]
    (fn [elem] (= elem n))
)

(defn removeEach [x elem]
    (remove (fnEquals? elem) x)
)

(defn containsString? [s ch]
    (not (empty? (filter true? (map = (createSeq ch (count s)) s))))
)

(defn akasztofa! [s lives]
    (println s)
    (if (and (not= () s) (not= lives 0))
        (
            (def guess (eval (read)))
            (if (containsString? s guess) (akasztofa! (removeEach s guess) lives) (akasztofa! s (- lives 1)))
        )
        ()
    )
)

(akasztofa! "hab" 10)

The output I get is this:

hab
(a b)
(b)
() 
Exception in thread "main" java.lang.ClassCastException: 
java.lang.Character cannot be cast to clojure.lang.IFn, compiling:
(/home/cicaharcos/Documents/Clojure/First/akasztofa/main.clj:38:1)

My input was: \h \a \b

Upvotes: 0

Views: 3387

Answers (2)

Ivan Grishaev
Ivan Grishaev

Reputation: 1681

You are trying to evaluate a character as a function. You should avoid using eval in your code. Instead, work with a list of symbols that come from read function. That is much safer than evaluating user's input.

Do not use def inside a function, only at the top of a module. Def declares a new entity globally among the entire namespace. Use let form that creates a local scope where its variables live until the evaluation exists from it.

Also, your function calls itself recursively. That's alright for short game sessions, but could cause troubles for long ones. In your case, the function akasztofa! fits the tail recursion optimization criteria (TRO) so that you may replace inner calls with recur form:

(recur (removeEach s guess) lives)
(recur s (- lives 1))))

Upvotes: 2

Alan Thompson
Alan Thompson

Reputation: 29958

The error comes from trying to evaluate a character as a function, for example:

(\a) => Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.Character cannot be cast to clojure.lang.IFn, 

I think it is the extra parens in the if statement that are trying to evaluate the character as a function. Remember, in Clojure parentheses are not "grouping" like in Java, they mean "function call".

Your code has some other problems, notable using () for an empty list. You must either quote the list like this:

'()

or, better yet, use an empty vector with square brackets (which requires no quoting):

 []

If you make the code look like the following, it seems to work:

(ns clojure.examples.hello
  (:gen-class))

(ns clojure-noob.core)

(defn takeFst [x n]
    (if (= n 0) [] (cons (first x) (takeFst (rest x) (- n 1))))
)

(defn createSeq [elem n]
    (if (= n 0) [] (cons elem (createSeq elem (- n 1))))
)

(defn fnEquals? [n]
    (fn [elem] (= elem n))
)

(defn removeEach [x elem]
    (remove (fnEquals? elem) x)
)

(defn containsString? [s ch]
    (not (empty? (filter true? (map = (createSeq ch (count s)) s))))
)

(defn akasztofa! [s lives]
    (println s)
    (if (and (not= [] s) (not= lives 0))
        (let [guess (eval (read))]
          (if (containsString? s guess)
            (akasztofa! (removeEach s guess) lives)
            (akasztofa! s (- lives 1))))
        [] ))

(akasztofa! "hab" 10)

Results:

hab
\h      ; <= user input plus <ret>
(a b)
\a      ; <= user input plus <ret>
(b)
\b      ; <= user input plus <ret>
()

Re Empty Lists:

Using the count function, you can see the problem:

demo.core=> (count ())
0

demo.core=> (count (\b))

ClassCastException java.base/java.lang.Character cannot be cast to clojure.lang.IFn  demo.core/eval16682 (form-init2403719904611886388.clj:1)

demo.core=> (count (1))

ClassCastException java.base/java.lang.Long cannot be cast to clojure.lang.IFn  demo.core/eval16686 (form-init2403719904611886388.clj:1)

demo.core=> (count '(\b))
1

So you can use () (unquoted) for an empty list (which I forgot), but it fails if it is non-empty unless you quote it. Using a vector is simpler and less error-prone:

demo.core=> (count [])
0
demo.core=> (count [\b])
1
demo.core=> (count [1])
1

Upvotes: 2

Related Questions