Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure: Adding functions to defrecord without defining a new protocol

Tags:

I'm used to OO in python/java. Doing Clojure now. I came across defrecord, but it seems like I have to define a protocol for each function or set of functions I want the record to implement. Creating a new protocol creates friction. I have to name not only the function I want, but the protocol. What I'am looking for is a way to "nicely" associate a function with a record so that the function has access to the record's parameters via the this parameter, without having to define a new protocol or add a function to an existing protocol.

like image 352
yalis Avatar asked Feb 17 '11 01:02

yalis


2 Answers

If you haven't tried multimethods yet, they may be closer to what you are looking for.

Define:

(defrecord Person [first middle last]) (defmulti get-name class) (defmethod get-name Person [person] (:first person)) 

Use:

(def captain (Person. "James" "T" "Kirk")) (get-name captain) 

The multimethod implementation that is chosen is based on the dispatch function in defmulti (a function that takes the args passed to the function and returns a dispatch value). Quite commonly "class" is the dispatch function, as here, to dispatch on type. Multimethods support multiple independent ad-hoc or Java-based type hierarchies, default implementations, etc.

In general though, I think perhaps you might want to take a step back and consider whether you really want either protocols or multimethods. You seem to be trying to "do OO" in Clojure. While aspects of OO (like polymorphism) are great, maybe you should try thinking in alternate ways about your problem. For example, in the example I just gave, there is no compelling reason (yet) to implement get-name polymorphically. Why not just say:

(defn get-name [x] (:first x)) 

Do you even need a Person record at all? Would a simple map suffice? Sometimes the answers are yes, sometimes no.

In general Clojure does not provide class inheritance. You can certainly build an equivalent (even with protocols) if you really want it but generally I find there are other better ways of solving that problem in Clojure.

like image 91
Alex Miller Avatar answered Oct 16 '22 14:10

Alex Miller


Excellent question.

As usual, there's a beautiful way to do things in Clojure - here's how to implement your own simple dynamic OO system (including inheritance, polymorphism and encapsulation) in 10 lines of Clojure.

The idea: You can put functions inside normals Clojure maps or records if you want, creating an OO-like structure. You can then use this in a "prototype" style.

; define a prototype instance to serve as your "class" ; use this to define your methods, plus any default values (def person-class   {:get-full-name      (fn [this] (str (:first-name this) " " (:last-name this)))})  ; define an instance by merging member variables into the class (def john    (merge person-class      {:first-name "John" :last-name "Smith"}))  ; macro for calling a method - don't really need it but makes code cleaner (defmacro call [this method & xs]   `(let [this# ~this] ((~method this#) this# ~@xs)))  ; call the "method" (call john :get-full-name) => "John Smith"  ; added bonus - inheritance for free! (def mary (merge john {:first-name "Mary"})) (call mary :get-full-name) => "Mary Smith" 
like image 23
mikera Avatar answered Oct 16 '22 14:10

mikera