4

I'm now making a class object with Clojure which has a method returning the object itself.

Written with Java, the object that I'd like to make is like,

class Point { public double x; public double y; public Point(double x, double y) { this.x = x; this.y = y; } public Point copy() { return new Point(this.x, this.y); } } 

The current clojure code that I wrote is like,

(ns myclass.Point :gen-class :prefix "point-" :init init :state state :constructors {[double double] []} :methods [[copy [] myclass.Point]])) (defn point-init [x y] [[] {:x x :y y}]) (defn point-copy [this] this) 

However, I got an error as follows.

java.lang.ClassNotFoundException: myclass.Point 

While I have googled about this issue, I couldn't find any answers. Does anyone know the solution for this issue?

Thank you in advance for your help.

2 Answers 2

6

I'm not sure it's the only way, but, in order to use the generated class type in a method signature, you can generate the class in 2 steps - though it's still in one file and compiled in one pass:

  • call gen-class with only the constructors
  • call gen-class again with state, full set of constructors and methods.

I tried with various methods including forward declaration, but only the above was working eventually. I did extend your example a little bit. Note That the copy method is not very useful as-is since Point is immutable, but you may want to provide mutators to your class.

Full listing:

(ns points.Point) ;; generate a simple class with the constructors used in the copy method (gen-class :name points.Point :init init :constructors {[] [] [double double] []}) ;; generate the full class (gen-class :name points.Point :prefix pt- :main true :state coordinates :init init :constructors {[] [] [double double] []} :methods [[distance [points.Point] double] [copy [] points.Point]]) (defn pt-init ([] (pt-init 0 0)) ([x y] [[] {:x x :y y}])) (defn pt-copy "Return a copy of this point" [this] (points.Point. (:x (.coordinates this)) (:y (.coordinates this)))) (defn pt-distance [^points.Point this ^points.Point p] (let [dx (- (:x (.coordinates this)) (:x (.coordinates p))) dy (- (:y (.coordinates this)) (:y (.coordinates p)))] (Math/sqrt (+ (* dx dx) (* dy dy))))) (defn pt-toString [this] (str "Point: " (.coordinates this))) ;; Testing Java constructors and method call on Point class (import (points Point)) (defn pt-main [] (let [o (Point.) p (points.Point. 3 4)] (println (.toString o)) (println (.toString p)) (println (.distance o p)) (println (.distance p (.copy p))))) 

In order to generate the classes, configure project.clj with the line

:aot [points.Point] 

Testing with lein gives:

tgo$ lein clean tgo$ lein compile Compiling points.Point tgo$ lein run Point: {:x 0, :y 0} Point: {:x 3.0, :y 4.0} 5.0 0.0 
Sign up to request clarification or add additional context in comments.

1 Comment

Sorry for the delay in responding but thank you very much for your kind answer. The code was quite helpful for me to revise my code!
0

Cause analysis

The issue is because the compiler doesn't know about myclass.Point in the :methods directive before the class myclass.Point is actually generated. Although this isn't an issue to a Java class but Clojure compiler doesn't seem to support this use case, (perhaps for good reasons.)

Solution

I feel it's much easier to implement interfaces (in the :implements directive) other than defining custom methods like your example. This might also suggest the good practice of "programming to interfaces." Generating interface from Clojure is doable, e.g. gen-interface. Just make sure the gen-interface is compiled aot, before the gen-class form.

Alternatively, I prefer to create a polyglot project with interfaces in Java and implementations in Clojure. Here's the code snippet to do so with leiningen:

// src/java/points/Point.java package points; public interface Point { public double distant(Point p); public Point copy(); } 
;; project.clj (defproject ;; Change these. The rests are the same. :aot [points.java-class] :source-paths ["src/clojure"] :java-source-paths ["src/java"]) ;; src/clojure/points/java_class.clj (ns points.java-class (:gen-class :name points.Point2D :implements [points.Point] ;; no more ClassNotFoundException :prefix "point-" :init init :state state :constructors {[double double] []}) ;; rests are the same 

This works because Leiningen compiles the java sources first by default.

This answer is also covered in my article, although I modified the code snippet to fit your question.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.