interop 2022-03-02

Is there a way to create a class with specific methods available via reflection at runtime?

I dont have an interface. I just need a class with two methods (that will be invoked via reflection)

using proxy generates a class and instance, but the methods on that class are not direct

instead it has __initClojureFnMappings, __updateClojureFnMappings and __getClojureFnMappings

I can generate a clean class with (ns ... (:genclass))

but I seem have issues with working with that inside the repl

is there any other way to do what ns is doing, but used like reify and proxy?

so I got it working with ns and compiled into an uberjar. But I can't seem to get a reflectable class instance in a jacked in repl.

just end up getting ClassNotFoundException on an import, or IllegalArgumentException: Unable to resolve classname: Resolver on attempting direct instantiation

I assume evaluating namespaces and forms in the repl does not compile classes and class load them?

@retrogradeorbit If you have an ns with (:gen-class) you need to explicitly compile that namespace.

How are you building the uberjar?

(mostly, the uberjar-building process will compile that namespace if you have the build steps configured properly)

with leiningen

So lein uberjar?

yep and that works

And you have :aot specified in project.clj with your ns that has the (:gen-class)? Or :aot :all? (I haven't used lein for years so I'm a bit rusty -- we switched to deps.edn four years ago at work).

yep :aot :all

OK, so what do you mean by "jacked in repl"?

so fire up a cider repl. jack in (launches lein repl).

then evaluate the calling namespace

Ah, I always start a REPL manually at the command-line and then connect my editor to it.

or the genclass namespace

You'll need to explicitly compile that namespace and you'll need classes on your classpath for it to be picked up.

ah ok... so if I lein repl from the command line it may have access to the gen-classed class?

can I compile that namespace from the repl?

(compile the.ns) in a REPL would work, regardless of how you start it

and that loads the class too?

or then I import it?

But I can't remember how to tell lein about the classes folder that needs to be on the classpath.

It's been too long since I used Leiningen to remember how to make this work with it.

(I don't use nREPL either, just a Socket REPL)

lein has a :prep-tasks option, as I recall, so you can get it to compile code prior to starting a REPL automatically. With that and having classes on the classpath, you should be fine. But you'll have to read the Leiningen docs for details.

OK, so I did lein new app crispin and then updated project.clj to look like this:

(defproject crispin "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url ""
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url ""}
  :dependencies [[org.clojure/clojure "1.10.1"]]
  :main ^:skip-aot crispin.core
  :prep-tasks ["compile"]
  :aot [crispin.core]
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all
                       :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})
and now when I start lein repl it compiles crispin.core (which has (:gen-class)) and I can (import crispin.core) in that REPL.

nice! thankyou!

Seems that lein generates classes into target/default/classes by default and that seems to be already on the classpath. So much "magic" happening in Leiningen 🙂

ok that worked! with the :prep-task and the :aot, the jack-in now works 🎉