Reputation: 93
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
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
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>
()
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