dtg
dtg

Reputation: 1853

How to correctly import user defined classes in Clojure

I am using Leiningen and Clojure and for the life of me I can't understand why Clojure makes it so difficult just to import namespaces correctly. This is the following error

This is what I have in my core.clj file:

; namespace macro
(ns animals.core
  (:require animals.animal)
  (:use animals.animal)
  (:import (animals.animal Dog))
  (:import (animals.animal Human))
  (:import (animals.animal Arthropod))
  (:import (animals.animal Insect)))

; make-animals will create a vector of animal objects
(defn make-animals []
  (conj []
        (Dog. "Terrier" "Canis lupis familiaris")
        (Human. "Human" "Homo sapiens")
        (Arthropod. "Brown Recluse" "Loxosceles reclusa")
        (Insect. "Fire Ant" "Solenopsis conjurata")))

; print-animals will print all the animal objects
(defn print-animals [animals]
  (doseq [animal animals]
    (println animal)))

; move-animals will call the move action on each animal
(defn move-animals [animals]
  (doseq [animal animals]
    (animals.animal/move animal)))

; entry to main program
(defn -main [& args]
  (let [animals make-animals]
    (do
      (println "Welcome to Animals!")
      (println "-------------------")
      (print-animals animals))))

Then, at the REPL, I enter the following (in the src/ directory of the lein project):

user> (require 'animals.core)
nil
user> (animals.core/-main)
ClassNotFoundException animals.core  java.net.URLClassLoader$1.run (URLClassLoader.java:202)

Okay... what? Why?

For reference, here is my file animal.clj also in the animals directory:

(ns animals.animal)

(defprotocol Animal
  "A simple protocol for animal behaviors."
  (move [this] "Method to move."))

(defrecord Dog [name species]
  Animal
  (move [this] (str "The " (:name this) " walks on all fours.")))

(defrecord Human [name species]
  Animal
  (move [this] (str "The " (:name this) " walks on two legs.")))

(defrecord Arthropod [name species]
  Animal
  (move [this] (str "The " (:name this) " walks on eight legs.")))

(defrecord Insect [name species]
  Animal
  (move [this] (str "The " (:name this) " walks on six legs.")))

Upvotes: 1

Views: 564

Answers (1)

Michał Marczyk
Michał Marczyk

Reputation: 84361

With your code pasted into a fresh Leiningen project, I get a different error due to a typo in -main: (let [animals make-animals] ...) should be (let [animals (make-animals)] ...). With this change, it all works fine:

user=> (require 'animals.core)
nil
user=> (animals.core/-main)
Welcome to Animals!
-------------------
#animals.animal.Dog{:name Terrier, :species Canis lupis familiaris}
#animals.animal.Human{:name Human, :species Homo sapiens}
#animals.animal.Arthropod{:name Brown Recluse, :species Loxosceles reclusa}
#animals.animal.Insect{:name Fire Ant, :species Solenopsis conjurata}
nil

Incidentally, it doesn't matter where exactly you invoke lein repl from as long as it's somewhere inside the project directory.

I'd venture a guess that there was something the matter with your namespace when you first tried to require it and now it won't load due to some namespace loading state in your REPL. You might want to try (require :reload 'animals.core) and if that doesn't work, restart your REPL. (You could also paste your entire REPL interaction up to the ClassNotFoundException somewhere if you run into it again.)

Also, about your ns form:

  1. You shouldn't both :require and :use the same namespace; :use already :requires it.

  2. It's more usual to use a single :import clause (in fact, a single clause per clause type); for example,

    (:import (animals.animal Dog Human Arthropod Insect))
    

    It's purely a matter of style in Clojure, but in ClojureScript it is in fact required by the language.

Upvotes: 2

Related Questions