0
votes

Clojure noob. I'm unable to get a basic example with :gen-class working.

$ lein new app pizza-parlor
; project.clj
(defproject pizza-parlor "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url "https://www.eclipse.org/legal/epl-2.0/"}
  :dependencies [[org.clojure/clojure "1.10.1"]]
  :main ^:skip-aot pizza-parlor.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all
                       :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})

; src/pizza_parlor/Deliverator.clj
(ns pizza-parlor.Deliverator
  (:gen-class))

(defn -deliver [pizza]
  (println "pipin' hot"))

$ lein repl
pizza-parlor.core=> (require 'pizza-parlor.Deliverator)
nil

pizza-parlor.core=> (def d (pizza-parlor.Deliverator.))
Syntax error (ClassNotFoundException) compiling new at (/tmp/form-init10318668819087633543.clj:1:1).
pizza-man.Deliverator

pizza-man.core=> (import pizza-parlor.Deliverator)
Execution error (ClassNotFoundException) at java.net.URLClassLoader/findClass (URLClassLoader.java:435).
pizza-parlor.Deliverator

I've tried the :aot option in project.clj and run lein compile to generate classes in target/default, but I get the same error.

What is the correct way to define a Java class via gen-class and then use it in the repl alongside the rest of my project code, or a test that I can run with lein test?

2

2 Answers

2
votes

You are misunderstanding how to execute a function in the REPL.

You do not need to "create a class" in Clojure. Just start up a repl and invoke a function. There are several choices:

(ns demo.core
  ; (:gen-class) ; uncomment to make runnable JAR file
  )

(defn -main []
  (println "main - enter")
  )

As the comment says, you only need the (:gen-class) expression in the ns form if you want to create an executable JAR file.

Method 1: Switch to the desired namespace and run the function:

~/expr/demo > lein repl
demo.core=> (in-ns 'demo.core)   ; single-quote is required
demo.core=> (-main)
main - enter

Method 2: Just invoke the function with a fully-qualified symbol:

~/expr/demo > lein repl
demo.core=> (demo.core/-main)   ; no quote!
main - enter

This will only work with the "main" ns, identified by the expression

:main demo.core

in project.clj.

Method 3: Require the namespace, then invoke the function with a fully-qualified symbol:

> lein repl
demo.core=> (require 'demo.core)   ; single-quote is required
demo.core=> (demo.core/-main)
main - enter

This will work for any namespace, even demo.core if the REPL places you in the user namespace.

Method #4: Build an executable uberjar

You must ensure that (:gen-class) is present in the (ns ...) form AND that you have :main demo.core in your project.clj. Then

~/expr/demo > lein uberjar                                                                         
Created /home/alan/expr/demo/target/uberjar/demo-art-0.1.0-SNAPSHOT.jar
Created /home/alan/expr/demo/target/uberjar/demo-art-0.1.0-SNAPSHOT-standalone.jar

~/expr/demo > java -jar /home/alan/expr/demo/target/uberjar/demo-art-0.1.0-SNAPSHOT-standalone.jar 
main - enter

If you see an error message like this:

~/expr/demo > java -jar /home/alan/expr/demo/target/uberjar/demo-art-0.1.0-SNAPSHOT-standalone.jar 
Error: Could not find or load main class demo.core
Caused by: java.lang.ClassNotFoundException: demo.core

then you forgot to include the (:gen-class) in the ns form.


Look here for more details on gen-class.


Update

If you really want to create a Java class from within Clojure code, you need the function gen-class , not the ns expression. See:


Update #2

Do you really need to generate a Java class from Clojure code? It might be easier to just write a Java class in a *.java source code file. Leiningen is perfectly able to compile mixed Clojure & Java source code into a single executable. This might be the easiest way to go.

1
votes

You have to pay attention to your names/folders with a dash - in it.

A clojure namespace of "some-clojure-namespace" will correspond to "some_clojure_namespace" in java world (and in folder structure)

So in your particular case :

; src/pizza-parlor/Deliverator.clj
(ns pizza-parlor.Deliverator

should be in "pizza_parlor" folder so :

; src/pizza_parlor/Deliverator.clj
(ns pizza-parlor.Deliverator

And then

(ns pizza-parlor.Deliverator)

would correspond to a (java) import :

(import pizza_parlor.Deliverator)

(note the underscore here)

But as stated by @Alan Thompson you do not need to create classes and import them in clojure. You can require the namespace and call it like he described. You want to generate Java Class and import them mostly if you have to interop with a java lib or so.

You can also use gen-class directly to generate any Java class that you would need like, see many example here