How do you use a type outside of its own namespace in clojure?
Asked Answered
D

2

14

I have a project set up with leiningen called techne. I created a module called scrub with a type in it called Scrub and a function called foo.

techne/scrub.clj:

(ns techne.scrub)
  (deftype Scrub [state]
    Object
     (toString [this]
     (str "SCRUB: " state)))

(defn foo
  [item]
  (Scrub. "foo")
  "bar")

techne/scrub_test.clj:

(ns techne.scrub-test                                                                                                                                             
  (:use [techne.scrub] :reload-all)                                                                                                                               
  (:use [clojure.test]))                                                                                                                                          


(deftest test-foo                                                                                                                                                 
  (is (= "bar" (foo "foo"))))                                                                                                                                                           

(deftest test-scrub                                                                                                                                               
  (is (= (Scrub. :a) (Scrub. :a)))) 

When I run the test, I get the error:

Exception in thread "main" java.lang.IllegalArgumentException: Unable to resolve    classname: Scrub (scrub_test.clj:11)
    at clojure.lang.Compiler.analyzeSeq(Compiler.java:5376)
    at clojure.lang.Compiler.analyze(Compiler.java:5190)
    at clojure.lang.Compiler.analyzeSeq(Compiler.java:5357)

If I remove test-scrub everything works fine. Why does :use techne.scrub 'import' the function definitions but not the type definitions? How do I reference the type definitions?

Demimonde answered 11/9, 2010 at 11:22 Comment(0)
D
16

Because deftype generates a class, you will probably need to import that Java class in techne.scrub-test with (:import [techne.scrub Scrub]) in your ns definition.

I actually wrote up this same thing with respect to defrecord here:

Another thing you could do would be to define a constructor function in scrub:

(defn new-scrub [state] 
  (Scrub. state))

and then you would not need to import Scrub in test-scrub.

Diarmid answered 11/9, 2010 at 12:42 Comment(3)
I always use constructor functions for this reason, and for validation.Broadway
Yep, we have found it helpful to extend defrecord to automatically add constructor functions with field validation, pprint support to an eval-able form, etc.Diarmid
Note that this answer pre-dates Clojure 1.4. Since 1.4, a positional (->Scrub) and map (map->Scrub) constructor will be automatically created by defrecord. That is the preferred method of construction and only requires that you refer those functions into your namespace - no need to import the class.Diarmid
V
2

I add the import, but get the same problem. I'm testing with the Expectations package 2.0.9, trying to import deftype Node and interface INode.

In core.clj:

(ns linked-list.core)

(definterface INode
  (getCar [])
  (getCdr [])
  (setCar [x])
  (setCdr [x]))

(deftype Node [^:volatile-mutable car ^:volatile-mutable cdr]
  INode
  (getCar[_] car)
  (getCdr[_] cdr)
  (setCar[_ x] (set! car x) _)
  (setCdr[_ x] (set! cdr x) _))

In core_test.clj:

(ns linked-list.core-test
  (:require [expectations :refer :all]
            [linked-list.core :refer :all])
  (:import [linked-list.core INode]
           [linked-list.core Node]))

and the output from lein autoexpect:

*************** Running tests ***************
Error refreshing environment: java.lang.ClassNotFoundException: linked-list.core.INode, compiling:(linked_list/core_test.clj:1:1)
Tests completed at 07:29:36.252

The suggestion to use a factory method, however, is a viable work-around.

Vilify answered 30/12, 2014 at 12:33 Comment(1)
I'm fairly certain that unlike :require, when you do :import, Clojure doesn't munge the path names. So "linked-list.core Node" should be "linked_list.core Node" for all the imports. I use Cursive+IntelliJ and it always auto-imports that way.Voiture

© 2022 - 2024 — McMap. All rights reserved.