Search code examples
clojure

clojure.lang.LazySeq cannot be cast to class clojure.lang.Associative


I'm new to Clojure and I tried to implement a genetic algorithm. Thus, I've got a problem, the implementation keeps throwing the following error:

class clojure.lang.LazySeq cannot be cast to class clojure.lang.Associative (clojure.lang.LazySeq and clojure.lang.Associative are in unnamed module 
of loader 'app')

To be mentioned that each function is tested individually in REPL and returns the correct results but after putting them all together, the error is thrown, and I don't understand it since it doesn't seems to specify a line number.

The version of clojure is the one from master branch and built with maven, on Windows.

The full code:

(ns ga)

(defn new-individual
    [genome-length]
    {:genome (vec (repeatedly genome-length #(rand-int 2))) :fitness 0}
    )


(defn fitness-function
    [genome, target]
    (Math/abs (- (reduce + genome) target))
)




(defn calculate-fitness
    [population, target]
    (defn fitness-function-helper 
        [individual, target]
        (assoc individual :fitness (fitness-function (individual :genome) target))
    )
        (map (fn [individual] (#(fitness-function-helper individual target))) population)
)



(defn crossover
        [first-individual, second-individual, crossover-rate, target]
        (let [new-genome (map (fn [i1,i2] (let [crossover-probability (rand)]
                                        (cond
                                            (<= crossover-probability crossover-rate) i1
                                            :else i2
                                        )
                                    )
                            ) 
            (first-individual :genome) (second-individual :genome)
                )]
            {:genome new-genome :fitness (fitness-function new-genome target)}
        )
        
)
        


(defn mutate
  [individual, genome-length, target]
  (let [new-genome (assoc (individual :genome) (rand-int genome-length) (rand-int 2))]
    {:genome new-genome :fitness (fitness-function new-genome target)}
  )
)



(defn better
  [i1 i2]
  (< (i1 :fitness) (i2 :fitness)))

(defn tournament-selection
  [population, population-size, steps, tournament-size, new-population, target]
  (if (< steps tournament-size)
    (recur population population-size (inc steps) tournament-size (conj new-population (nth population ((comp rand-int -) population-size 2))) target
      
     )
        ;(println new-population)
        (first (sort better (calculate-fitness new-population target)))
        
    )
)

(defn new-generation [population, population-size, crossover-rate, target, tournament-size]
    (loop [steps 0 new-population ()]
        (if (< steps population-size)
        (let [i1 (tournament-selection population population-size 0 tournament-size () target)]
            (let [i2 (tournament-selection population population-size 0 tournament-size () target)]
                (let [offs (crossover i1 i2 crossover-rate target)]
                    (recur (inc steps) (conj new-population offs))
                )
            )
        )
        new-population
    )
        )
    )
    
    (defn new-mutated-generation [population, population-size, genome-length, target]
        (loop [steps 0 new-population ()]
            (if (< steps population-size)
                    (recur (inc steps) (conj new-population (mutate (nth population steps) genome-length target)))
                    new-population
            )

        )

    )
        

(defn evolve [population-size, genome-length, target]
(let [population (calculate-fitness (repeatedly population-size #(new-individual genome-length)) target)]
    (let [offsprings (new-generation population population-size 0.5 target 5)]
        (println (new-mutated-generation offsprings population-size genome-length target))
        )
    


)

)

(evolve 10 5 5)


Solution

  • A stacktrace reveals that the problematic code is the line

    (let [new-genome (assoc (vec (individual :genome)) (rand-int genome-length) (rand-int 2))]
    

    and more specifically, the call to assoc. If we edit the code by inserting the following line just above:

    (println "Individual: " (individual :genome) ", " (class (individual :genome)))
    

    it prints out

    Individual:  (0 1 1 0 1) ,  clojure.lang.LazySeq
    

    The problem is that assoc cannot be used with lazy sequences (clojure.lang.LazySeq) because it does not implement the clojure.lang.Associative interface which is needed by assoc.

    This lazy sequence is constructed by the call to map on this line:

    (let [new-genome (map (fn [i1,i2] (let [crossover-probability (rand)]
    

    If you replace map by mapv so that the code looks like this

    (let [new-genome (mapv (fn [i1,i2] (let [crossover-probability (rand)]
    

    the code will work.