clojure

How does clojure decide to which type a function belongs?


Consider this snippet:

(def vertices [
  -0.5 -0.5 0
  0.5 -0.5 0
  0 0.5 0])

(def vertex-buffer (doto (BufferUtils/createFloatBuffer (count vertices))
                     (dorun (map #(.put (float %)) vertices))))

which yields this error:

No matching field found: put for class java.lang.Float

How does clojure decide that put should belong to Float (and not the FloatBuffer created above)?

How can I tell clojure that put belongs to FloatBuffer?

If this approach is not as elegant as I thought it would be, I wouldn't mind a nudge in the right direction.


Solution

  • In Java, you would call .put with one argument:

    buffer.put(1);
    

    In Clojure, you have to provide buffer as the first argument:

    (.put buffer 1)
    

    If you call multiple such methods in Java, you can chain them:

    buffer.put(1).put(2).put(3);
    

    In Clojure, you would have to repeat word buffer:

    (.put buffer (float 1))
    (.put buffer (float 2))
    (.put buffer (float 3))
    

    Or nest those calls, starting from the last method:

    (.put (.put (.put buffer (float 1)) (float 2)) (float 3))
    

    With doto, you will avoid verbosity and increase readability:

    (doto (BufferUtils/createFloatBuffer 3)
                  (.put (float 1))
                  (.put (float 2))
                  (.put (float 3)))
    

    doto is a macro that:

    Evaluates x then calls all of the methods and functions with the
    value of x supplied at the front of the given arguments. The forms
    are evaluated in order. Returns x.

    Your code will be transformed into:

    (dorun (BufferUtils/createFloatBuffer (count vertices))
             (map #(.put (float %)) vertices))
    

    Put still has only one argument and dorun has two.

    You can use let + run! here:

    (def buffer (let [buffer (BufferUtils/createFloatBuffer (count vertices))]
                  (run! #(.put buffer (float %)) vertices)
                  buffer))
    

    Buffers also have bulk .put, so you can do this:

    (def buffer (.put (BufferUtils/createFloatBuffer (count vertices))
                      (float-array vertices)))
    

    And if you plan to call .get or .write after that, don't forget to .flip:

    (def buffer (doto (BufferUtils/createFloatBuffer (count vertices))
                  (.put (float-array vertices))
                  (.flip)))