Using records from a different namespace in Clojure

10

This is something that took me a bit of exploration to figure out so maybe this will help someone.

Records are a new feature of Clojure 1.2 that allow you to dynamically generate a compiled Java class with a specific set of fields that can be used as a map with :keyword keys for the fields. It’s still in a bit of flux but it has a lot of nice improvements over the older struct support.

The defrecord macro creates a Java class under the hood and is instantiated just like a Java class (at least for now):

user=> (defrecord Nachos [cheese jalapenos])
user.Nachos
user=> (Nachos. "cheddar" "yes")
{:cheese "cheddar",
 :jalapenos "yes"}

If you create some records in a particular namespace, say in food/nachos.clj:

(ns food.nachos)
(defrecord Nachos [cheese jalapenos])

then to access that record from another ns, you will definitely need to import the Java record, but also require the namespace to ensure that the class is actually generated. You might be able to get around that require with :gen-class, I’m not sure.

(ns food.feast
  (:require [food.nachos])
  (:import [food.nachos Nachos]))

...

I think that’s right, but please correct me if I’m misunderstanding.

Comments

10 Responses to “Using records from a different namespace in Clojure”
  1. Daniel Glauser says:

    Thanks for writing this up Alex, I can’t say I’ve had a good handle on what defrecord does before I read this.

    I’d be surprised if there’s no shorthand for this. If there isn’t I wouldn’t be surprised if someone creates one. I wonder how hard it would be to define a macro to include a new keyword, something like:

    (ns foot.feast
    (:import [Nachos, CheeseDip, Guacamole :from food.nachos]))

    Food for thought…
    Daniel

  2. Meikel says:

    Yes. This is the right approach. gen-class won’t help, because it suffers from the same problem. (see here: http://bit.ly/a22Mar)

    However, since the record stems from a well-defined namespace (which is not the case for gen-class), a more convenient way to handle records as dependencies could be imagined.

  3. Brent Millare says:

    If you want to be as dynamic as possible, you could just use reify and refer to the instance’s class with (class x). You could then tie down the class to a var. The downside to this approach is to create an instance of the class you need to use eval.

    (def x (reify FooProtocol (foo [this] ‘foo)))
    (def cx (class x))
    (def y (eval `(new ~cx)))

  4. Brent Millare says:

    Actually, just talked to Chouser, there is a way without eval and since reify produces an instance from a class with a ctorr with no args, you can use (. newInstance classname) so the above becomes

    (def cx (class x))
    (def y (.newInstance cx))

  5. Brent Millare says:

    Also,

    You can just wrap a reify in a defn and get most of the benefits of defrecord. Functions are compiled and that defn will always return a instances all of the same class.

  6. Chas Emerick says:

    I remember suggesting a while back that require be invoked for the namespaces corresponding to each imported class. That was back when gen-class was the only game in town, so now I’d suggest adding the namespaces corresponding to the package of imported classes. Then (import ‘some.package.YourRecord) would automatically attempt to require some.package.

  7. Thank you Alex!!! I spent a couple hours trying to figure out what in the heck I was missing when Clojure couldn’t find my record. Appreciate your blog post for having that info out there in the public space.

  8. Brian Marick says:

    Here’s a fun fact. Suppose you have a file t_protocol_helper.clj. In Clojure 1.2.1 and 1.3, you *may* require that file using either dashes (which will make angels sing your Lispy praises) or underscores (which will make every kitten in the world save Larry Ellison’s burst into tears):

    (:use behaviors.t-protocols-support) ; OK
    (:use behaviors.t_protocols_support) ; OK

    When importing a defrecord class, you *must* use underscore:

    (:import behaviors.t-protocols-support.R) ; You just wasted your morning! Yay!
    (:import behaviors.t_protocols_support.R) ; Yes, you! Knuckle under to the Will of Java!

  9. Alex says:

    @Brian – yes, good point. When you are in “class” context, though shalt use _ and when you are in clojure context thou shalt use -. There was actually a breaking change around this behavior in 1.2.1 (at least it broke our 1.2.0 code :) where protocols with – would create classes with -, which worked fine in the dynamic classloader but created invalid .class files during AOT. I think that change made this worse.

    I haven’t been able to get 1.3 to work yet for our code but I’m hopeful that the record constructor functions will make the import needs go away so we can hide this ugliness.

  10. Anon says:

    Using the constructor ->RecordName (generated by new records in recent Clojure versions) works across namespaces. I think the constructor exists for that reason. The following works:

    ;; Define a protocol in namespace1
    (ns example.namespace1)
    (defprotocol Namespace1Protocol
    (example-fn [this]))

    ;; Use the protocol to define a record in namespace2:
    (ns example.namespace2
    :use [example.namespace1 :only (Namespace1Protocol)])
    (defrecord Namespace2Record [a b c]
    Namespace1Protocol
    (example-fn [this] (str “Hello world.”)))

    ;; Create an instance of the record from namespace2 in namespace3:
    (ns example.namespace3
    :use [example.namespace2 :only (->Namespace2Record)])
    (def my-record (->Namespace2Record 1 2 3))

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!