Using gen-class to generate methods with same names and arities but different type signatures

823 views
Skip to first unread message

David Greenberg

unread,
Aug 2, 2012, 4:41:59 PM8/2/12
to clo...@googlegroups.com
Hi Clojurians,
I'm finding myself far down the rabbit hole of gen-class. I am trying
to generate a class that has a bunch of static methods, and each of
those methods has many overloads of arities and types. Unfortunately,
there is no interface--this class gets called through reflection in a
legacy system.

The class's parameter types include primitives, primitive arrays, and Objects.

I am doing something like:

(gen-class
:name "my.odd.Class"
:main false
:methods [^{:static true} [-myfunc ["[Lint;" String] void]
^{:static true} [-myfunc ["[Ldouble;" int] Object]])

I found a post explaining that I could define method implementations
with overloads by doing -methodName-arg1type-arg2type-arg3type, but
when I try that I get an exception that the FileName is too long from
the clojure compiler.

I can easily generate a map from signatures to implementations, but I
need to generate the class with all the overloads.

Is there any way to do this? Should I resign myself to writing out a
.java file, and compiling that?

Thanks,
David

Seth Chandler

unread,
Aug 2, 2012, 9:08:56 PM8/2/12
to clo...@googlegroups.com
Don't have an answer but I would sure enjoy hearing the group's wisdom on this.

Christian Sperandio

unread,
Aug 3, 2012, 1:33:50 AM8/3/12
to clo...@googlegroups.com
Hi,

The problem may be come from the name of the class.
Do you try to change the name of your class? For example, replace
Class by Foo or another word.

I'm not sure it's the correct answer :$


2012/8/2 David Greenberg <dsg123...@gmail.com>:
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

Marshall T. Vandegrift

unread,
Aug 3, 2012, 4:41:31 AM8/3/12
to clo...@googlegroups.com
David Greenberg <dsg123...@gmail.com> writes:

> I can easily generate a map from signatures to implementations, but I
> need to generate the class with all the overloads.
>
> Is there any way to do this? Should I resign myself to writing out a
> .java file, and compiling that?

In my experience just writing a stub Java class for this sort of
situation is the simplest solution. I've found that `gen-class` works
well for interop when needing to extend an abstract base class or
similar scenarios. But when the need is less to interop with the JVM
than specifically with Java, it is far easier to wire from Java into
Clojure than vice versa.

-Marshall

Daniel Solano Gómez

unread,
Aug 3, 2012, 10:11:06 AM8/3/12
to clo...@googlegroups.com
Hello, David,

Well, gen-class certainly supports creating static methods with type
hints and overloaded arities. An example using gen-class as part of ns:

(ns gen-class-test.StaticTest
(:gen-class :methods [^:static [foo [ints int] String]
^:static [foo [longs long] String]
^:static [foo [chars char] String]
^:static [foo [shorts short] String]
^:static [foo [booleans boolean] String]
^:static [foo [floats float] String]
^:static [foo [doubles double] String]
^:static [foo ["[Ljava.lang.Object;" Object] String]
^:static [foo ["[Ljava.lang.String;" String] String]
^:static [foo ["[Ljava.lang.String;" int] String]
^:static [foo [boolean] String]
^:static [foo [char] String]
^:static [foo [short] String]
^:static [foo [int] String]
^:static [foo [long] String]
^:static [foo [float] String]
^:static [foo [double] String]
^:static [foo [Object] String]
^:static [foo [String] String]]
:main false))

This will generate a class with the following signature:

public class gen_class_test.StaticTest extends java.lang.Object{
public static {};
public gen_class_test.StaticTest();
public java.lang.Object clone();
public int hashCode();
public java.lang.String toString();
public boolean equals(java.lang.Object);
public static java.lang.String foo(int[], int);
public static java.lang.String foo(long[], long);
public static java.lang.String foo(char[], char);
public static java.lang.String foo(short[], short);
public static java.lang.String foo(boolean[], boolean);
public static java.lang.String foo(float[], float);
public static java.lang.String foo(double[], double);
public static java.lang.String foo(java.lang.Object[], java.lang.Object);
public static java.lang.String foo(java.lang.String[], java.lang.String);
public static java.lang.String foo(java.lang.String[], int);
public static java.lang.String foo(boolean);
public static java.lang.String foo(char);
public static java.lang.String foo(short);
public static java.lang.String foo(int);
public static java.lang.String foo(long);
public static java.lang.String foo(float);
public static java.lang.String foo(double);
public static java.lang.String foo(java.lang.Object);
public static java.lang.String foo(java.lang.String);
}

Now, when it comes to what this class is doing, it is calling the
Clojure function -foo with the arguments from the static method
invocation. In particular:

1. All primitive arguments will be boxed.
2. The number of arguments for -foo must match the number of arguments
for the static method invocation. For multiple arities, you can have
a variadic function or a function with multiple arities.
3. Unfortunately, there is no way to call a different function for each
arity/type.

However, you can make -foo a multimethod. The following works as an
implementation for the above:

(defmulti -foo
(fn [& args]
(apply vector (map class args))))

; one-arg invocations
(defmethod -foo [Boolean] [_] "boolean")
(defmethod -foo [Character] [_] "char")
(defmethod -foo [Short] [_] "short")
(defmethod -foo [Integer] [_] "int")
(defmethod -foo [Long] [_] "long")
(defmethod -foo [Float] [_] "float")
(defmethod -foo [Double] [_] "double")
(defmethod -foo [Object] [_] "Object")
(defmethod -foo [String] [_] "String")

; two-arg invocations
(defmethod -foo [(class (boolean-array 0)) Boolean] [_ _] "booleans")
(defmethod -foo [(class (char-array 0)) Character] [_ _] "chars")
(defmethod -foo [(class (short-array 0)) Short] [_ _] "shorts")
(defmethod -foo [(class (int-array 0)) Integer] [_ _] "ints")
(defmethod -foo [(class (long-array 0)) Long] [_ _] "longs")
(defmethod -foo [(class (float-array 0)) Float] [_ _] "floats")
(defmethod -foo [(class (double-array 0)) Double] [_ _] "doubles")
(defmethod -foo [(class (into-array [""])) String] [_ _] "Strings")
(defmethod -foo [(class (to-array [])) Object] [_ _] "Objects")
(defmethod -foo [(class (into-array [""])) Integer] [_ _] "Strings + int")


Perhaps there is a better way to do it, but this is the best I could
come up with. I hope it helps.

Sincerely,

Daniel
signature.asc

dgrnbrg

unread,
Aug 3, 2012, 7:50:01 PM8/3/12
to clo...@googlegroups.com
I ended up digging deep through gen-class, and I learned about an interesting, undocumented feature that solves this problem:

You can, in fact, overload methods of the same arity on type, and here's how:

Each method you define in gen-class tries to lookup a corresponding var in the impl-ns of the form {impl-ns}/{prefix}{method-name}

However, if the method is overloaded on type, gen-class first looks up a var of the form {impl-ns}/{prefix}{method-name}{typesig}, and only if that fails does it use the default var.

typesig is constructed in the following way:

(str (interleave (repeat \-) (map typesig-name types))

where types is the vector of types passed to the method declaration.

Finally, here's a way to define typesig-name (and I'm assuming all arguments are Classes)

(defn typesig-name [c]
  (cond (.isArray c) (str (typesig-name (.getComponentType c)) "<>")
           (.isPrimitive c) (comment this should give "int", "float", "double", "long", etc)
           (.getSimpleName c)))

If you provide vars with those names, you can overload by arity.

To recap, these are the quirks:
1) If you don't overload a method, you must provide the implementation in the var of the same name.
2) If you do overload the arity, you can optionally provide the implementation in the specially named vars, but if they don't exist, they'll fall back to vars of the same name.
3) The overload vars have dash-separated type signatures included in their name, where primitives are written like in java, arrays end in "<>", and you only include the simple name of the classes.

Whew...

p.s. unfortunately, clojure still boxes the arguments into these function no matter what. So this is a dispatch optimization, not a boxing optimization (or, in my case, allows me to generate the correct interop forms).

Daniel Solano Gómez

unread,
Aug 3, 2012, 8:58:59 PM8/3/12
to clo...@googlegroups.com
You're right. I wasn't aware of this functionality. So, my previous
example can be implemented using:

(defn -foo-boolean<>-boolean [_ _] "booleans")
(defn -foo-char<>-char [_ _] "chars")
(defn -foo-short<>-short [_ _] "shorts")
(defn -foo-int<>-int [_ _] "ints")
(defn -foo-long<>-long [_ _] "longs")
(defn -foo-float<>-float [_ _] "floats")
(defn -foo-double<>-double [_ _] "doubles")
(defn -foo-String<>-String [_ _] "Strings")
(defn -foo-Object<>-Object [_ _] "Objects")
(defn -foo-String<>-int [_ _] "Strings + int")
(defn -foo-boolean [_] "boolean")
(defn -foo-char [_] "char")
(defn -foo-short [_] "short")
(defn -foo-int [_] "int")
(defn -foo-long [_] "long")
(defn -foo-float [_] "float")
(defn -foo-double [_] "double")
(defn -foo-String [_] "String")
(defn -foo-Object [_] "Object")

One interesting difference between this approach and the multimethod
approach is compile-time vs runtime-time dispatching. With a
multimethod, doing something like (StaticTest/foo (Long. 2)) returns
"long" instead of "Object", which may or may not be desirable
behaviour.

On the other hand, it hides some quirks in Clojure's compile-time
resolution. For example, calling (StaticTest/foo \a) actually invokes
StaticTest.foo(Object) instead of StaticTest.foo(char).

Thanks for the extra insight!

Sincerely,

Daniel
signature.asc
Reply all
Reply to author
Forward
0 new messages