Last active
December 15, 2015 22:19
-
-
Save mguinada/5332626 to your computer and use it in GitHub Desktop.
A clojure simple object system form Manning's Clojure in Action.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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