Thursday, 16 October 2014

Closures are the poor man's objects

I've heard this a lot without giving too much thought about it. But the Circuit Breaker made me contemplate the idea a bit. So....creating a stateful object in Clojure is actually pretty easy. The simplest I can think of is a unique id generator. This would look something like this in java

interface IdGenerator {
Integer nextId();
}
class SimpleIdGenerator implements IdGenerator {
private final AtomicInteger generator; = new AtomicInteger(0);
public Integer nextId() {
return generator.incrementAndGet();
}
}
In Clojure

(defn create-id-generator
"Returns an impure function to generate ids"
[]
(let [next-id (atom 0)]
(fn []
(swap! next-id inc)
@next-id)))
;; here we create our "object"
(def generate-id! (create-id-generator))
(println "Id: " (generate-id!))
;; Id: 1
(println "Id: " (generate-id!))
;; Id: 2
(println "Id: " (generate-id!))
;; Id: 3
;; here we create another "object"
(def generate-id-2! (create-id-generator))
(println "Id2: " (generate-id-2!))
;; Id2: 1
Of course it's a boring example so I've come up with another, slightly more interesting one, a simplified vending machine.

(defn create-vending-machine
"Returns an impure function to act as an object representing a vending machine"
[]
(let [items (atom {:Coke {:quantity 3 :price 5}
:Mars {:quantity 2 :price 3}
:Sandwich {:quantity 5 :price 10}})
wallet (atom 0)]
(fn [item-id money]
(cond
(nil? (item-id @items)) :invalid-item
(zero? (get-in @items [item-id :quantity])) :out-of-stock
(not= money (get-in @items [item-id :price])) :price-not-match
:else (do
(swap! items update-in [item-id :quantity] dec)
(swap! wallet + money)
:success)))))
;; the closure, or "object"
(def buy! (create-vending-machine))
(defn assert= [a b]
(assert (= a b)))
(assert= :invalid-item (buy! :Pepsi 1))
(assert= :price-not-match (buy! :Coke 20))
(assert= :success (buy! :Mars 3))
(assert= :success (buy! :Mars 3))
(assert= :out-of-stock (buy! :Mars 3))

An epitome of applied Single Responsibility Principle. Does only one thing and hides its internals completely. Of course I could leverage dynamic typing to overload the function and perform any kind of other logic - getting information of the content, changing the state, whatever. I could make a mess, but I would have to awkwardly work against the language to do it.

No comments :

Post a Comment