user1091344
user1091344

Reputation: 740

How to cope with different return types of a Clojure function when called by Java?

I want to call the binomial Clojure function from Java. One problem I encounter is that it returns different data types, either long (e. g., n=5, k=3) or BigInt (e. g., n=20, k=10). On Java side, it should be a BigInteger.

There are at least two options to overcome this, which is preferable?

  1. Force Clojure function to return BigInt (I don't know if this is possible. I tried type hints, but it still returns either long or BigInt).
  2. In Java, use Pattern match on return value and check type, then convert appropriate.

Clojure

(ns sample.hello
  (:import (clojure.lang BigInt)))

(defn binomial
      "Calculate the binomial coefficient."
      ^BigInt [^Integer n ^Integer k]
      (let [a (inc n)]
           (loop [b 1
                  c 1]
                 (if (> b k)
                   c
                   (recur (inc b) (* (/ (- a b) b) c))))))

Java

public class Hello {
    public static final IFn binomial;

    static {
        Clojure.var("clojure.core", "require").invoke(Clojure.read("sample.hello"));
        binomial = Clojure.var("sample.hello", "binomial");
    }

    public static BigInteger binomial(int n, int k) {
        Object a = binomial.invoke(n, k);
        return ((BigInt) a).toBigInteger();
    }
}

Upvotes: 2

Views: 151

Answers (2)

Alan Thompson
Alan Thompson

Reputation: 29958

Please note that type hints (emphasis on the work "hint") are only used in Clojure for compiler optimizations, and cannot coerce types (or even warn if the wrong type is present!). I would never use them unless you have profiled with Criterium etc.

Having said that, I would tweak Martin's answer just a bit:

(defn binomial
  "Calculate the binomial coefficient."
  [n k]
  (biginteger
    (let [a (inc n)]
      (loop [b 1
             c 1]
        (if (> b k)
          c
          (recur (inc b) (* (/ (- a b) b) c)))))))

This emphasizes that the result of the let block will always be coerced to BigInteger. Also, it removes the deceptive "casting" to type Integer at the beginning.

If you are really worried a user may provide non-integer arguments, use an assert or similar as the first line of the function:

(assert (and (int? n) (int? k))) ; or `integer?` or `pos-int?`

It is also often helpful to coerce input args to a known type, which can bypass the combinatorical explosion of "maybes"

(defn binomial
  "Calculate the binomial coefficient."
  [n k]
  (assert (and (pos-int? n) (pos-int? k))) 
  (let [n (biginteger n)
        k (biginteger k)
        a (inc n)]
    (loop [b 1
           c 1]
      (if (> b k)
        c
        (recur (inc b) (* (/ (- a b) b) c))))))

In this case, the coercion of the return type is unnecessary, since math operations on BigInteger values will always produce another BigInteger.

Upvotes: 1

Martin Půda
Martin Půda

Reputation: 7568

You can also use the function biginteger to force Clojure to return java.math.BigInteger (and avoid converting in Java):

(defn binomial
  "Calculate the binomial coefficient."
  [^Integer n ^Integer k]
  (let [a (inc n)]
    (loop [b 1
           c 1]
      (if (> b k)
        (biginteger c)
        (recur (inc b) (* (/ (- a b) b) c))))))

Tests:

(class (binomial 5 3))
=> java.math.BigInteger
(class (binomial 20 10))
=> java.math.BigInteger

Upvotes: 5

Related Questions