Protocol Oriented Programming
Priyatam Mudivarti
Principal, Facjure
Platform Engineer @LevelMoney
@priyatam
Clojure/Remote
Feb 2016
Rationale
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
Programs are Data + Operations
data extensibility
operation extensibility
How do they extend?
what are its variants?
Clojurescript announcement
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
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.
Core Values
polymorphism
expression-problem
new abstractions
But First ...
monkey patching!
oo Programmer
I monkey patch the heck out of my Ruby & Python Code. What are you talking about?
Clojure/cLJS
programmer
Your function can't handle new types in the future that you're not aware of.
(Def Monkey-Patching)
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
Python
class Array
def sum
inject {|sum, x| sum + x }
end
end
RUBY
(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
Js used MP to provide backwards compatibility and new capabilities (ex., DOM libs)
The expression problem
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
without modifying the source
Protocols
in Clj, CLJS
(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
CREATE A NEW PROTOCOL + TYPE
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!"
ADD New functions over EXISTING types
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
add NEW Protocols to AN EXISTING type
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.
A complete example
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
add protocols to a number of types at once.
(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)))))))
generate an instance ofanonymous type
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))))))
Reusing implementations: extend/specify
REVISITING Polymorphic dispatch
source: https://cloud.github.com/downloads/stuarthalloway/clojure-presentations/ClojureProtocolsJAOO.pdf
protocols
are cooL,
okay?
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"
wait, there's more!
DEFTYPE, DEFRECORD, REIFY, PROXY, GEN-CLASS
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/
Choosing the right Clojure Type
But I'm A Functional PRogrammer!
I now work on
25k LOC
Have you seen Clojure in prod?
Such is life
Schema
Prismatic's Schema has excellent support for describing and validating data shapes.
RECORDS, Component
Stuart Sierra's Component Pattern provides a foundation to manage runtime state, and control behavior.
structure & Integration of Clojure CODE
INTERop is good
React/Om/Reagent/...
Netty/Aleph
CSS/Garden
...
System of components
Daniel Szmulewicz's System provides a good start for building system of components
data extensibility
operation extensibility
(Protocols)
(deftype, defrecord)
patterns
in clj, cljs, and in the wild
(code samples)
non-monolithic
organic growth
two logical abstractions
operating together
conformance
in a dynamic world
Decouple
from the context
requirements for modeling behavior
data extensibility
operation extensibility
(Protocols)
(deftype, defrecord)
component
SYSTEM
EVENT
NETWORK
...
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
The question Is not OO vs FP
but realizing good ideas work with eachother
CREDITS & References
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
Protocol Oriented Programming, in Clojure, Cljs
By Priyatam Mudivarti
Protocol Oriented Programming, in Clojure, Cljs
Clojure and Cljs expose their core data structures and hosts using Protocols. However, typical webapps built on Clojure/Cljs have seldom used Protocols to abstract domain concepts. Thanks to Component, Om.Next, and the announcement of Swift as "Protocol Oriented Language" new abstractions are emerging.
- 2,161