Skip to content

Instantly share code, notes, and snippets.

@mguinada
Last active December 15, 2015 22:19
Show Gist options
  • Save mguinada/5332626 to your computer and use it in GitHub Desktop.
Save mguinada/5332626 to your computer and use it in GitHub Desktop.
A clojure simple object system form Manning's Clojure in Action.
(ns clojure-testdrive.object)
(declare OBJECT)
(declare ^:dynamic this)
(defn new-object [klass]
"Creates an object instance of a given class"
(let [state (ref {})]
(fn thiz [command & args]
(condp = command
:class klass
:class-name (klass :name)
:set! (let [[k v] args]
(dosync (alter state assoc k v))
nil)
:get (let [[key] args]
(key @state))
(let [method (klass :method command)]
(if-not method
(throw (RuntimeException. (str "Unable to repspond to " command))))
(binding [this thiz]
(apply method args)))))))
(defn method-spec [sexpr]
"Defines a method"
(let [name (keyword (second sexpr)) body (next sexpr)]
[name (conj body 'fn)]))
(defn method-specs [sexprs]
"Defines a collection of methods"
(->> sexprs
(filter #(= 'method (first %)))
(mapcat method-spec)
(apply hash-map)))
(defn lookup-method [method-name klass]
(or ((klass :methods) method-name)
(if-not (= #'OBJECT klass)
(lookup-method method-name (klass :parent)))))
(defn new-class [class-name parent methods]
"Creates a class"
(fn klass [command & args]
(condp = command
:new (new-object klass)
:name (name class-name)
:parent parent
:methods methods
:method (let [[method-name] args]
(lookup-method method-name klass)))))
(def OBJECT (new-class :OBJECT nil {}))
(defn parent-class-spec [sexprs]
"The exteded class specification"
(let [extends-spec (filter #(= 'extends (first %)) sexprs)
extends (first extends-spec)]
(if (empty? extends)
'OBJECT
(last extends))))
(defmacro defclass [class-name & specs]
"Class definition macro"
(let [parent-class (parent-class-spec specs)
fns (or (method-specs specs) {})]
`(def ~class-name (new-class '~class-name #'~parent-class ~fns))))
(defclass Person
(method age []
(* 2 10))
(method greet [visitor]
(str "Hello " visitor))
(method about [diff]
(str "I was born about " (+ diff (this :age)) " years ago")))
(defclass Women
(extends Person)
(method greet [visitor]
(str "Howdy " visitor))
(method age []
(* 2 9)))
(def cindy (Person :new))
(def nancy (Person :new))
(def nina (Women :new))
(nancy :greet 'John')
(nina :age)
(nina :about 1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment