4
votes

I want to create an object with properties and methods in Clojure, I read that gen-class and proxy can do the job I need but its implementation is very confusing for me.

I want to use proxy to avoid AOT compilation steps, I read about it and I though I better learn how to use the easier of the two

Here is what I want to do in Clojure

Java code:

public class MyClass {
    public float myFloat;

    MyClass( float _myFloat ) {
        myFloat = _myFloat
    }

    public void showNumber() {
        println( myFloat );
    }
}

I'm struggling to translate that code to Clojure using proxys, any help will be much appreciated


UPDATE:

Apparently deftype is more suitable for my purposes, but I'm still struggling with its implementation

Here is my Clojure code:

(deftype Particle [x y]
  Object
  (render [this]
    (no-stroke)
    (fill 200 30 180)
    (ellipse x y 200 200)))

Thing is I need to specify a protocol which I'm not sure which one to use, so I'm using Object as I'm trying to create a java-class like object but I get the folloiwng error message:

Can't define method not in interfaces: render

I'm using quill which is a Processing port for Clojure if that helps


UPDATE 2:

OK I manage to get a working defprotocol and deftype combo, but there is 1 more thing I need to leran how to do and that is to add member variables or properties to my class, here is my clojure code:

(defprotocol ParticleProtocol
  (update [this])
  (render [this]))

(deftype Particle [position]
  ParticleProtocol
  (update [this])
  (render [this]
    (no-stroke)
    (fill 200 30 180)
    (ellipse (.x position) (.y position) 20 20)))

To this object I would like to add a couple of variables like radius among others, any ideas?

2
Offtopic: if you're going to develop in Clojure, you have to stop thinking the object oriented way.m0skit0

2 Answers

3
votes

I agree that deftype (or possibly defrecord) is a better than proxy to do this in Clojure, but see my comments at the end to consider all possibilities.

For your question after UPDATE 2.

You can add "properties" to records by specifying them in the arglist:

(deftype Particle [position radius prop3 prop4]
   ...
 )

Remember that types in Clojure are immutable, so there is no concept of setting properties after creating the entity. If some of the properties are optional, it is recommended best practice to create helper "factory" methods, like:

(defn make-particle 
  ([position] (Particle. position nil nil nil))
  ([position radius] (Particle. position radius nil nil))
  ;; etc. add more here as needed
  )

An option to consider is to drop types entirely and just use maps, which have within them whatever "properties/fields" you need. Types are useful when you need to implement abstractions. For your ParticleProtocol - what is the value it is providing? Protocols are meant to provide a way to have polymorphism, so will you have multiple implementations of this protocol?

Chas Emerick did an in depth flowchart of how to choose a data type in Clojure that may help you: http://cemerick.com/2011/07/05/flowchart-for-choosing-the-right-clojure-type-definition-form/


[Update showing example map implementation]:

To construct a map with a "property" and retrieve that property you would do:

(def mymap {:myfloat 3.1415926})
(println "myfloat has value:" (:myfloat mymap))

To provide additional functionality, such as a "render" function, just create a fn that accepts a map with the desired keys:

;; the details are bogus, just showing the syntax
(defn render [m]
  (no-stroke)
  (fill (:radius m) (:position m))
  (do-something-else (:position m)))      

For your update, if you meant to update the values in the particle map, then you need to create a new map, rather than updating an existing one.

(def myparticle {:position 100 :radius 25})

(defn change-pos [particle-map new-pos]
  (assoc-in particle-map [:position] new-pos))

(let [new-particle (change-pos myparticle 300)]
  (println new-particle))
;; prints out {:position 300 :radius 25}
;; orig myparticle still has value {:position 100 :radius 25}

;; or do it directly
(println (assoc myparticle :position 300))
;; prints out {:position 300 :radius 25}
1
votes

You can add the "variables" next to position, like this:

(deftype Particle [position radius]
   ...
  )

position and radius aren't really variables, they are more like final attributes. If you need to "vary" them, you should store atoms in them, like this:

(Particle. (atom (Position. 3 4)) (atom 5.0))

But you should really heed the advise of @m0skit0 to stop thinking in terms of objects and classes and start thinking in functions and immutable data structures.