erdos
erdos

Reputation: 3538

clojure function call hides type information

I have a small function used for debugging:

(set! *warn-on-reflection* true)

(defn debug [x] (doto x (->> println :>)))

When I call my function in a loop, I get the following reflection warning:

(loop [i 5] (when (pos? i) (recur (debug (dec i)))))

form-init14269737395101875093.clj:1 recur arg for primitive local: i is not matching primitive, had: Object, needed: long
Auto-boxing loop arg: i

I want to solve the reflection warning. How can I make my function "inherit" the type information from the parameter without explicitly specifying it or replacing it with a macro?

Upvotes: 0

Views: 171

Answers (3)

Rulle
Rulle

Reputation: 4901

If, for whatever the reason, you do not want to use a macro, you may want to have a look at definline which seems to preserve type information:

(definline debug2 [x] (doto x (->> println :>)))

The call below, for instance, does not result in a reflection warning:

(.add (debug2 (ArrayList.)) 5)

My initial thought would have been to use a Java class with overloaded methods to achieve something along these lines, of which one method would take a generic argument T and return a T. In addition to this generic method, you would have had to overload that method for the few primitive types because generics only work with boxed values AFAIK. You could then reuse the class below for your debugging purposes:

import clojure.lang.IFn;

public class PassThrough {
    private IFn _fn;
    
    public PassThrough(IFn fn) {
        _fn = fn;
    }

    public <T> T invoke(T x) {
        _fn.invoke(x);
        return x;
    }

    public long invoke(long x) {
        _fn.invoke(x);
        return x;
    }
    
    public double invoke(double x) {
        _fn.invoke(x);
        return x;
    }
}

This will not work for reference types, though, because of type erasure. So if I would do something like this, I would still get a warning:

(defn debug [x] (doto x (->> println :>)))
(def pt-debug (PassThrough. debug))
(.add (.invoke ^PassThrough pt-debug (ArrayList.)) 9) ;; <-- Reflection warning here when calling .add

Upvotes: 0

leetwinski
leetwinski

Reputation: 17849

the problem is that the return type of debug can't be deduced.

this is usually solved with type hints

in your case the following should do the trick:

(defn debug ^long [x] (doto x (->> println :>)))

user> (loop [i 5] (when (pos? i) (recur (debug (dec i)))))
4
3
2
1
0
nil

Upvotes: 1

Alan Thompson
Alan Thompson

Reputation: 29984

Here is a way that works:

(loop [i (Integer. 5)]
  (when (pos? i)
    (recur (debug (dec i)))))

with a warning-free result:

lein test tst.demo.core
4
3
2
1
0

It looks like using just plain 5 causes the compiler to use a primitive, which can't be type hinted. Explicitly creating an Integer object sidesteps the problem. I also tried (int 5) which didn't work.

Is there a reason you want to turn on reflection warnings? I normally never use them, especially for debugging.


Update

Note that if you wrap the code in a function like so:

(defn stuff
  [arg]
  (loop [i arg]
    (when (pos? i)
      (recur (debug (dec i))))))

there is no problem calling (stuff 5) since function args must always be passed as objects (via autoboxing if necessary).

Upvotes: 1

Related Questions