An alternative class-based object-system for Clojure based on hash-maps and multimethods.
fenrir
is an alternative class-based object-system for Clojure based on hash-maps and multimethods.
Simply add this to your leiningen deps: [fenrir "0.1.0"]
The documentation can be found here: http://eduardoejp.github.com/fenrir/
To be honest, I don't have a good reason for creating Fenrir. Clojure already has an object system based on protocols, records and types and I think that the whole idea of coding to interfaces instead of coding to concrete implementations is great. I'm a big fan of Clojure protocols. I made Fenrir as some sort of entertaining side project that I could use to play around.
However, even though this was made mostly for fun, it's fully functional and has various interesting features.
(defclass fShape [] []
(get area -1)
(get perimeter -1))
(defclass fSquare [fShape] [side]
(get area (* side side))
(get perimeter (* side 4)))
(defclass fCircle [fShape] [radius]
(get diameter (* radius 2))
(get area (* Math/PI (* radius radius)))
(get perimeter (* Math/PI (get-slot *self* :diameter))))
This code illustrates some of the features of Fenrir: Slots, Virtual Slots, Inheritance (which can be multiple) & Polymorphism
Before I explain each feature, please evaluate the following sexps:
(def s (ctor fShape))
(get-slot s :area)
(get-slot s :perimeter)
(def sq (ctor fSquare :side 5))
(get-slot sq :side)
(get-slot sq :area)
(get-slot sq :perimeter)
(def cir (ctor fCircle :radius 6))
(get-slot cir :radius)
(get-slot cir :diameter)
(get-slot cir :area)
(get-slot cir :perimeter)
Instantiation is done through the ctor method (which can be overwritten to create custom constructors). The get-slot method provides access (getters are created
through the 'get' "special form"). The set-slot method provides mutation (setters are created through the 'set' "special form").
The default ctor implementation takes the object's slots as :keywords.
The *self*
variable refers to the object (like 'this' in Java) inside method definitions, getters & setters.
Another example will help illustrate more features of Fenrir:
(defclass fPet [] [name age owner]
"A pet of any kind."
(make-noise [] "Make some pet noise."))
(defclass fDog [fPet] []
"A dog of any race."
(set age (if (< *val* 15) *val* -1)) ; If the dog is too old, put it to sleep...
(get name (str owner "'s dog, " name))
(make-noise [] (println "Bark!")))
(defclass fMachine [] [name manufacturer]
"A machine of some kind."
(get name (str name ", Copyright (C) " manufacturer " 2011"))
(explode [] (println "BOOM!")))
(defclass fRoboDog [fDog fMachine] [name]
"fDog 2.0"
(ctor [name age owner manufacturer]
(base-ctor *fclass* :name name :age age :owner owner :manufacturer manufacturer))
(make-noise [] (println "*Buzz* Bark! *Buzz*"))
(mask name fMachine))
Before explaining everything, try this:
(def pet (ctor fPet :name "Claws" :age 5 :owner "Mr. Foo"))
(make-noise pet)
(def dog (ctor fDog :name "Rex" :age 5 :owner "Mr. Bar"))
(make-noise dog)
(get-slot dog :name)
(set-slot dog :age 3)
(set-slot dog :age 18)
(def machine (ctor fMachine :name "IPhone" :manufacturer "Apple Inc."))
(get-slot machine :manufacturer)
(get-slot machine :name)
(def robodog (ctor fRoboDog "TRex" 10 "Ms. Baz" "Bots R Us"))
(make-noise robodog)
(get-slot robodog :name)
Here we can see method definitions, getters & setters, multiple inheritance, slot clashing, ctor definition and masking.
*val*
.*fclass*
argument.Well folks, that's all.
Even though Fenrir works, I actually use Clojure's default object system (the protocols, types & records one). It's very nice once you get to know it. If, however, you feel that a class-based system is what your heart craves for, download Fenrir and play with it :-)