Reputation: 740
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?
long
or BigInt
).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
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
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