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