Rafael Mosca
Rafael Mosca

Reputation: 13

How to keep a list of objects in Clojure?

I'm trying to use java interop, to create a list of objects.

I have tried for and doseq, and they both have problems.

for is lazy, and I need to create all the objects, as they interact with each other internally.

(def master-object (MasterObject.))

(for [x [1 10 50 99]]
  (let [child (.createChildObject master-object)]
    (.setCoefficient child x)
    child))

doseq creates all, but don't return a list.

(doseq [x [1 10 50 99]]
  (let [child (.createChildObject master-object)]
    (.setCoefficient child x)
    child))

I was thinking about using loop and recur, but was wondering if could be a more idiomatic way of doing that.

Thanks

Upvotes: 1

Views: 612

Answers (2)

Thumbnail
Thumbnail

Reputation: 13473

The magic word for realising lazy sequences is doall. If you wrap it round your for, you'll get the effect you want:

(doall
  (for [x [1 10 50 99]]
    (let [child (.createChildObject master-object)]
      (.setCoefficient child x)
      child)))

=>
(#object[interop.core.ChildObject 0x326037f1 "interop.core.ChildObject@326037f1"]
 #object[interop.core.ChildObject 0x759b711 "interop.core.ChildObject@759b711"]
 #object[interop.core.ChildObject 0x2bc06dcb "interop.core.ChildObject@2bc06dcb"]
 #object[interop.core.ChildObject 0x4a37a35a "interop.core.ChildObject@4a37a35a"])

Notice that the returned object is a LazySeq,not a list. And the REPL forces its realisation anyway.

If setCoefficient returns Java this, you can abbreviate the above to

(doall
  (for [x [1 10 50 99]]
    (.. master-object
        (createChildObject)
        (setCoefficient x))))

The structure of your code worries me. You are implementing half the association between MasterObject and ChildObject - a child knows its master; but not the other half - a master does not know its children. We don't see you do anything with the generated collection of children. If that is so, nothing refers to them, and they are garbage that will be disposed of whenever.

I think the MasterObject should keep a collection of its children, added to when the ChildObject is created in createChildObject. Then you needn't keep the collection of created children, and doseq is to be preferred to for. I've done it entirely in Clojure interop as follows:

(ns interop.core)

(definterface ChildInterface
  (setCoefficient [x]))

(definterface MasterInterface
  (createChildObject [])
  (getChildren []))

(deftype ChildObject [master ^:unsynchronized-mutable coefficient]
  Object
  (toString [this]
    (str "Child " coefficient))
  ChildInterface
  (setCoefficient [this x]
    (set! coefficient x)
    this))

(deftype MasterObject [^:unsynchronized-mutable children]
  Object
  (toString [this]
    (str "Master children: " (.seq children)))
  MasterInterface
  (createChildObject [this]
    (let [child (ChildObject. this nil)]
      (set! children (conj children child))
      child))
  (getChildren [this]
    children))

(def master-object (MasterObject. []))

(doseq [x [1 10 50 99]]
  (.. master-object
      (createChildObject)
      (setCoefficient x)))

This is still pretty ugly, as I haven't worked out how to suppress the default rendering of Java objects:

master-object
=>
#object[interop.core.MasterObject
        0x3f7683a
        "Master children: (#object[interop.core.ChildObject 0xb1cf4bb \"Child 1\"] #object[interop.core.ChildObject 0x16b56f70 \"Child 10\"] #object[interop.core.ChildObject 0x5dadc8ab \"Child 50\"] #object[interop.core.ChildObject 0x6f22f049 \"Child 99\"])"]

I've used a Clojure vector for the children. In this context, a Java collection would do just as well.

Upvotes: 0

Chris Murphy
Chris Murphy

Reputation: 6509

If you need to create all the objects then I think it would be idiomatic to return a vector rather than a list, for example:

(vec (for [x (range 3)]
       x))

There are a few different ways to force all the output from a for. The above is one of them. vec is just short for into []. So if you definitely need a realised list you could instead:

(into '() (for [x (range 3)]
            x))

For creating a list of objects doseq will not help you as it is only about 'side effects'.

Really you could look at using map for what you want to accomplish. Make a separate mapping function and map over it:

(defn make-child [x]
  (let [child (.createChildObject master-object)]
      (.setCoefficient child x)
      child))

(map make-child [1 10 50 99])

Upvotes: 2

Related Questions