Priyatam Mudivarti
Principal, Facjure
Platform Engineer @LevelMoney
@priyatam
Since the 70's we've been living in an object-oriented world.
The engineers whose shoulders we stand have built code on Objects.
Don't fight the System.
Building bridges between static & dynamic worlds is a fascination design space
— Dave Abrahams, Lead, Swift sdk
data extensibility
operation extensibility
How do they extend?
what are its variants?
NYC Clojure
Dec 16 2012
I took a step back and said, what part of Java did I need in order to implement Clojure and its data structures, what could I do without, and what semantics was I willing to support - for Clojure - i.e. not in terms of interop. What I ended up with was - a high-performance way to define and implement interfaces.
— Rich Hickey
Protocols are a mechanism in Clojure/Cljs for specifying re-usable type interfaces.
Clojure's types and protocols are dynamic type classes ... — fogus
source: https://news.ycombinator.com/item?id=1401871
Think polymorphism without hierarchy.
I monkey patch the heck out of my Ruby & Python Code. What are you talking about?
Your function can't handle new types in the future that you're not aware of.
Fixing bugs in libraries you don't control when the maintainer is unresponsive to your pleas.
Replacing someone's method despite lack of a pre-existing contract.
Modify the behavior of some methods, but you don’t want to alter its source code.
Imagine a world where every programmer you know is a wannabe language designer, bent on molding the language to their whims. When I close my eyes and imagine it, I have a vision of the apocalypse, a perfect, pitch-black storm of utterly incomprehensible, pathologically difficult to debug code.
— Jeff Atwood (Coding Horror)
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
class Array
def sum
inject {|sum, x| sum + x }
end
end
(function($){
// store original reference to the method
var _old = $.fn.method;
$.fn.method = function(arg1,arg2){
if ( ... condition ... ) {
return ....
} else { // do the default
return _old.apply(this,arguments);
}
};
})(jQuery);
Python is strict about allowing monkeypatching. (can't modify 'Object')
Ruby's black magic!
Js used MP to provide backwards compatibility and new capabilities (ex., DOM libs)
The Expression Problem is a new name for an old problem. The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety (e.g., no casts)
— Philip Wadler
source: https://cloud.github.com/downloads/stuarthalloway/clojure-presentations/ClojureProtocolsJAOO.pdf
(almost identical)
Writing to protocols gives you a dynamic,
open, extensible system not tied to derivation, and is fast, and a great way to architect the polymorphic part of your designs
— Rich Hickey
(defprotocol IFoo
(foo [this])
(bar [this])
(deftype Foo1 []
IFoo
(foo [this] (println "Foo1"))
(bar [this] (println "Bar1")))
(deftype Foo2 []
IFoo
(foo [this] (println "Foo2"))
(bar [this] (println "Bar2")))
=> (foo (Foo1.))
Foo1
nil
=> (foo (Foo2.))
Foo2
nil
basic OO polymorphism
(deftype Foo [])
(deftype IncompatibleBar [])
(defprotocol ExpressYourself
(foos-and-bars-in-harmony [this]))
(extend-type Foo
ExpressYourself
(foos-and-bars-in-harmony [this] "I am a foo!"))
(extend-type IncompatibleBar
ExpressYourself
(foos-and-bars-in-harmony [this] "I am an incompatible bar!"))
=> (foos-and-bars-in-harmony (Foo.))
"I am a foo!"
=> (foos-and-bars-in-harmony (IncompatibleBar.))
"I am an incompatible bar!"
ie., new functions are defined in a Protocol
(defprotocol IFoo
(foo [this]))
(deftype FooItAgain []
IFoo
(foo [this] (println "same old foo.")))
(defprotocol INewFoo
(a-new-fn [this])
(extend-type FooItAgain
INewFoo
(a-new-fn [this] (println "realized I needed this!")))
=> (foo (FooItAgain.))
same old foo.
nil
=> (a-new-fn (FooItAgain.))
realized I needed this!
nil
ie., extend behavior from outside
(defprotocol Sushi
(wash-my-hands [this])
(eat-sushi [this]))
(defn deal-with-sushi [eater]
(wash-my-hands eater)
(eat-sushi eater))
(deftype American []
Sushi
(wash-my-hands [this]
(println "Er, where's the sink?"))
(eat-sushi [this]
(println "Hey, I'm hip, I'll use chopsticks!")))
(deftype Japanese []
Sushi
(wash-my-hands [this]
(println "Oshibori for the win."))
(eat-sushi [this]
(println "Actually dude we just use our fingers.")))
=> (deal-with-sushi (American.))
Er, where's the sink?
Hey, I'm hip, I'll use chopsticks!
=> (deal-with-sushi (Japanese.))
Oshibori for the win.
Actually dude we just use our fingers.
source: http://davedellacosta.com/cljs-protocols
(deftype YetAnotherFoo []
IFoo
(foo [this] (println "yep.")))
(deftype FooMeToTheMoon []
IFoo
(foo [this] (println "...")))
(extend-protocol INewFoo
YetAnotherFoo
(a-new-fn [this] (println "need it here too."))
FooMeToTheMoon
(a-new-fn [this] (println "...and here.")))
=> (a-new-fn (YetAnotherFoo.))
need it here too.
nil
=> (a-new-fn (FooMeToTheMoon.))
...and here.
nil
(defn mouse-view [app owner]
(reify
om/IWillMount
(will-mount [_]
(let [mouse-chan
(async/map
(fn [e] [(.-clientX e) (.-clientY e)])
[(listen js/window EventType/MOUSEMOVE)])]
(go (while true
(om/update! app :mouse (<! mouse-chan))))))
om/IRender
(render [_]
(dom/p nil
(when-let [pos (:mouse app)]
(pr-str (:mouse app)))))))
a custom, one-off implementation of some Protocol, without caring or storing the type for re-use.
(defn ^:private to-cursor* [val state path]
(specify val
IDeref
(-deref [this]
(if-not *read-enabled*
(get-in @state path)
(throw (js/Error. (str "Cannot deref cursor during render phase: " this)))))
ICursor
(-path [_] path)
(-state [_] state)
ITransact
(-transact! [this korks f]
(transact* state (into path korks) f this))
IEquiv
(-equiv [_ other]
(check
(if (cursor? other)
(= val (-value other))
(= val other))))))
source: https://cloud.github.com/downloads/stuarthalloway/clojure-presentations/ClojureProtocolsJAOO.pdf
safe, disciplined, "gorilla" patching
every function in a protocol is namespaced
provide static checks
avoid reflection
decouple from the context
plugin hooks for instrumentation
extensions vs inheritance "hierarchies"
Clojure provides a fast, well-designed system right out of the box for handling these situations succinctly. Unlike existing methods which are error prone or tedious (monkey patching and retooling dispatch every time, as in your approach), Protocols are elegant and trivial.
— Dave Fayram, CTO @LevelMoney
see "gorilla patching and protocols"
Choosing the right Clojure Type doesn't have to be hard if you don't care about interop
(use defrecord)
But ...
http://cemerick.com/2011/07/05/flowchart-for-choosing-the-right-clojure-type-definition-form/
I now work on
25k LOC
Have you seen Clojure in prod?
Such is life
Prismatic's Schema has excellent support for describing and validating data shapes.
Stuart Sierra's Component Pattern provides a foundation to manage runtime state, and control behavior.
React/Om/Reagent/...
Netty/Aleph
CSS/Garden
...
Daniel Szmulewicz's System provides a good start for building system of components
data extensibility
operation extensibility
(code samples)
data extensibility
operation extensibility
1. Combining several simple ideas into one compound one
—John Locke, An Essay Concerning Human Understanding ”
3. Separating them from all other ideas that accompany them in their real existence: this is called abstraction, and thus all its general ideas are made.
The acts of the mind, wherein it exerts its power over simple ideas, are chiefly these three*
2. Bringing two ideas, together, and setting them by one another so as to take a view of them at once, without uniting them, but by relating them.
*also the first paragraph in SICP
but realizing good ideas work with eachother
Slides http://slides.com/priyatam/protocol-oriented-programming
B&W Architecture Photography Kai Ziehl http://www.kaiziehlphotos.com
"Introducing Clojurescript", Rich Hickey, https://www.youtube.com/watch?v=tVooR-dF_Ag
"Datatypes & Protocols", Rich Hickey, https://groups.google.com/forum/#!topic/clojure/_Ecf6MfxfB8
"Protocol Oriented Programming in Swift", Dave Abraham https://developer.apple.com/videos/play/wwdc2015-408/ "Clojure Protocols", Stuart Sierra, http://www.ibm.com/developerworks/library/j-clojure-protocols/ "Cljs Protocols", Dave Della Costa, http://davedellacosta.com/cljs-protocols "Monkey patching and Gorilla Engineering", Dave Fayram — https://news.ycombinator.com/item?id=1401871 "Expression Problem revisited", Mads Torgersen — http://www.daimi.au.dk/~madst/ecoop04/main.pdf "Clojure's solution to EP", Chouser, http://www.infoq.com/presentations/Clojure-Expression-Problem "Clojure Protocols", Stuart Halloway, https://cloud.github.com/downloads/stuarthalloway/clojure-presentations/ClojureProtocolsJAOO.pdf "Choosing the right types", Chas Emerick, http://cemerick.com/2011/07/05/flowchart-for-choosing-the- right-clojure-type-definition-form/ "Reloaded Components", Daniel Szmulewicz, https://github.com/danielsz