1

Using Clojure, suppose t represents a Java object, and I have a collection of [ts].

How can I map .setter to [ts]? I'm having trouble using (map #(.setter %1 %2) ts [vals]). When I access the associated getters afterwards, I return a list of nils.

1
  • You might also like clojure.core/memfn Commented Jul 13, 2015 at 20:04

2 Answers 2

2

As an example of ArrayList Java class. I create three of those, and then I add "s1" to the first one, "s2" to the second and "s3" to the third. This is my "setter".

Then I read the first value of each, and I expect to get "s1", "s2", "s3" - so that's a getter in this example.

(let [ts [(java.util.ArrayList. 1)
          (java.util.ArrayList. 1)
          (java.util.ArrayList. 1)]]
  ; Add one element to each of the ArrayList's
  (doall (map #(.add %1 %2) ts ["s1" "s2" "s3"]))

  ; Verify that elements are indeed added.
  (doall (map #(.get %1 0) ts)))

This example works as expected - the latter mapv returns ("s1" "s2" "s3").

Why the similar approach would not work for you? Well, my strong suspiction is that's because you use map which returns a lazy sequence.

map returns a lazy sequence, which means that it will not evaluate unless you try to get/use the values produced by it - and you usually don't need a return value from a setter. This means that your setter would be never called.

That's why I've used doall - it will take a lazy sequence and realize it, meaning that each element will get computed (in your case - each setter will get called).

My example, when I use only map and not doall when setting elements, would fail:

(let [ts [(java.util.ArrayList. 1)
          (java.util.ArrayList. 1)
          (java.util.ArrayList. 1)]]
  ; Use a lazy map here - without doall
  (map #(.add %1 %2) ts ["s1" "s2" "s3"])
  (doall (map #(.get %1 0) ts)))

; java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

It fails indeed, because no element got added.

Alternatively, you can use a mapv variant that returns a vector and hence is always not lazy:

(let [ts [(java.util.ArrayList. 1)
          (java.util.ArrayList. 1)
          (java.util.ArrayList. 1)]]
  ; Use a lazy map, but realize sequence using doall
  (doall (map #(.add %1 %2) ts ["s1" "s2" "s3"]))
  (mapv #(.get %1 0) ts))

As pointed out by @John Wiseman in the comment, though, mapv is probably a worse choice because it's not always clear that it's used to force realization, while doall makes it clear and obvious.


A word about lazy sequences:

A lazy seq is a seq whose members aren't computed until you try to access them.

(source)

It's a good thing to learn more about lazy seqs, as they're a powerful tool used all over in Clojure codebases. One somewhat counter-intuitive thing they bring to the table is an ability to create infinite sequences, i.e. (range) creates an infinite sequence of all numbers starting with 0. So it's perfectly legal to do:

(map #(* % %) (range))

It will create an infinite lazy sequence of squares of all the numbers!

Sign up to request clarification or add additional context in comments.

5 Comments

I wouldn't recommend using mapv just to avoid laziness--it obscures the programmer's intention. doall has the one purpose, and if I see doall in code I know precisely why it's there.
@JohnWiseman - good point, I'll rearrange the answer to make it the default choice.
In this case I'd use doseq instead of doall. Given that OP is mutating the collection in-place, there is no need to generate another intermediate collection that gets discarded.
I thought doseq at first, but there's the collection of vals and I wasn't sure how to best handle them. (doseq [[t v] (map vector ts vals)] (.setter t v)) seems kinda pointless.
dorun is preferable to doall if you are not accessing the return value of the operation. And in Clojure 1.7 you can use run! instead of (dorun (map ...)) (except in this case, because sadly unlike map run! does not take an arbitrary args list)
2

It sounds like the setter method isn't returning the modified t object. If you wrote setter, you could modify it so that it does, or you will just have to hold on to your original ts (but also make sure to use dorun to squeeze out the laziness of map):

(let [ts ...]
  (dorun (map #(.setter %1 %2) ts [vals]))
  (println "Modified ts:" ts))

An alternative, if it would be much more convenient for you to have the map return the collection, is to do something like this:

(dorun (map #(do (.setter %1 %2) %1) ts [vals]))

1 Comment

I changed doall to dorun based on @noisesmith's comment on another answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.