Search code examples
clojure

clojure.lang.Cons cannot be cast to Clojure.lang.Ifn


Hi everybody I've been recently trying to learn to a new language and I sort of bumped into Clojure which look like a real interesting language because I've never heard about functional programming, even though I had used JavaScript before that kind of leverages it, well I'm gonna stop with the small talk and get into the problem.

I've been working on solving the https://github.com/gigasquid/wonderland-clojure-katas and more specific on the doublets problem. I think I have came with a solution but it send me the error on the title of this post. I have read about this error and it seems that it triggers when you want the compiler expects a function but it doesn't. Here is the full code of my solution to see if you can help me out with this one:

(ns doublets.solver
  (:require [clojure.java.io :as io]
            [clojure.edn :as edn]
            [clojure.set :as set]))

(def words (-> "words.edn"
               (io/resource)
               (slurp)
               (read-string)))

(defn linked-word [word word-list]
  (some #(when (= (count (set/difference (into #{} (seq %))
                                         (into #{} (seq word)))) 1) %)
        word-list))

(defn doublets [word1 word2]
  (let [n (count word1) v (cons word1 (filter #(= (count %) n)
                              (remove #{word1} words)))]
    (tree-seq #(and (linked-word (% 0) %) (not= (% 0) word2))
              #(cons (linked-word (% 0) (rest %))
                (remove #{(% 0)} (rest %))) v)))

As you can see cons is a function so the error doesn't seem to be the case described above.


Solution

  • I can reproduce the error after downloading the words.edn file and running with (doublets "bank" "loan"). I think the problem is these expressions:

     (% 0)
    

    which you have in a few places. I see that you are cons-ing some things, so that may be a clue. What is (% 0) supposed to do? If you want the first char, just say (first xyz) or something.

    I would also break out the anonymous functions #(...) and give them real names.

    Update

    My guess seems to be correct as this experiment shows:

    (cons 1 [2 3]) => (1 2 3)
    (class (cons 1 [2 3])) => clojure.lang.Cons
    
    (vec (cons 1 [2 3])) => [1 2 3]
    (class (vec (cons 1 [2 3]))) => clojure.lang.PersistentVector
    

    OK, rewrite like:

    (defn doublets [word1 word2]
      (let [n (count word1)
            v (vec (cons word1 (filter #(= (count %) n)
                                 (remove #{word1} words))))]
        (tree-seq
          #(and
             (linked-word (% 0) %)
             (not= (% 0) word2))
          #(vec (cons (linked-word (% 0) (rest %)))
             (remove #{(% 0)} (rest %)))
          v)))
    

    new error: java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol

    And there is the clue we needed!

    We are evaluating things as symbols, not strings! The problem is read-string, which is how you read source code, not data like strings. Delete read-string:

    (def words (-> "words.edn"
                 (io/resource)
                 (slurp)))
    

    We now get a new error on this line:

        v (vec (cons word1 (filter #(= (count %) n)
                             (remove #{word1} words))))]
    
    ERROR in (dotest-line-40) (RT.java:664)
    Uncaught exception, not in assertion.
    expected: nil
      actual: java.lang.UnsupportedOperationException: 
                 count not supported on this type: Character
    

    So your seq has created something like "foo" => [\f \o \o], and you then try to say (count \f). You can't count a single char, only a string.

    I'll let you debug it from there.