Clojure Protocols and Expression Problem

70 views
Skip to first unread message

Saager Mhatre

unread,
Aug 28, 2013, 10:58:56 AM8/28/13
to clj-...@googlegroups.com
I guess this would be directed more to BG, but others should feel free to jump in.

Even after I left the workshop this weekend, the Expression Problem and Clojure's solution in the form of Protocols still didn't reify (chuckle!) in my head.

If we stick to the example on the c2 wiki, here's how we'd put in the initial code:

(ns fu.shape)

(defprotocol Shape
  "Describes geometry of a shape"
  (area [x] "Area encomapssed within shape"))

(deftype Square [side]
  Shape
  (area [this] (* side side)))

(deftype Circle [radius]
  Shape
  (area [this] (* Math/PI radius radius)))

And the Triangle type would be implemented thusly:

(ns fu.more-shape
  (:require [fu.shape]))

(deftype Triangle [base height]
  fu.shape/Shape
  (area [this] (/ (* base height) 2)))

What I still don't get is how we could implement the perimeter function without having to touch the fu.shape namespace.
Also, wouldn't adding perimeter to the Shape protocol require a redefinition(?) of all existing types as they would no longer be extending Shape satisfactorily?

Feel free to get back to me with any queries/comments.
- d

Sidhant Godiwala

unread,
Aug 29, 2013, 12:12:18 AM8/29/13
to clj-...@googlegroups.com
Hi Saager,

What protocols let you do is dynamically add interfaces to types.

In this case, perimeter would be a function in another protocol

(defprotocol ClosedShape
  (perimeter [this]))

You can now extend already existing types to this new protocol without modifying
the fu.shape namespace

(extend-type fu.shape/Square
  ClosedShape
  (perimeter [this]
    (* 4 (.side this))))

This doesn't change the Shape contract in anyway and more importantly doesn't modify Square, Circle and Triangle for 
anybody else (monkey patching)

on a side note, you could use extend-protocol to implement an interface for several types at once.

(extend-protocol ClosedShape
  fu.shape/Circle
  (perimeter [this] ...)
  
  fu.shape/Square
  (perimeter [this] ...)

Baishampayan Ghose

unread,
Aug 29, 2013, 1:29:37 AM8/29/13
to clj-...@googlegroups.com
Saager,

Sidhant pointed it out correctly, perimeter itself needs to be a
protocol function for this to work as expected. If perimeter was not a
part of the Shape protocol to begin with you can always create a new
protocol for this. ~BG
> --
> You received this message because you are subscribed to the Google Groups
> "Clojure Users Group Pune" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to clj-pune+u...@googlegroups.com.
> Visit this group at http://groups.google.com/group/clj-pune.
> For more options, visit https://groups.google.com/groups/opt_out.



--
Baishampayan Ghose
b.ghose at gmail.com

Anil W

unread,
Aug 29, 2013, 12:29:34 PM8/29/13
to <Unnamed>
Hi BG, Siddhant,

I have a question about Siddhant's line in the comment

" more importantly doesn't modify Square, Circle and Triangle for 
anybody else (monkey patching)"

This may be noob question - Will the existing objects (or instances) for Square, Circle and Triangle get newly added perimeter function? when you do

(extend-protocol ClosedShape
  fu.shape/Circle
  (perimeter [this] ...)
  
  fu.shape/Square
  (perimeter [this] ...)

  fu.shape/Triangle
  (perimeter [this] ...)


I am also playing with it to find it out.. but your answer would help

--
Thanks,
Anil
Best,
Anil

Sidhant Godiwala

unread,
Aug 29, 2013, 12:56:03 PM8/29/13
to clj-...@googlegroups.com
Hey Anil,

It's not Circle, Square and Triangle getting an implementation of the function but perimeter
learning how to work with these types.

Protocols separate behaviour, contract and type vs interfaces in java which just pull out contract 
leaving type+behaviour in objects

Ravindra Jaju

unread,
Aug 30, 2013, 12:54:53 AM8/30/13
to clj-...@googlegroups.com
Nice discussion, guys.

I tend to think of protocols/multi-methods this way - to put it really crudely - since
it helps me overcome the first hurdle in understanding.

;; Presenting foo - the super-dude function which is expected to handle many types of 'arguments.'
(defn foo [arg1 & more]
  ;; fork execution logic based on some properties of arg1
  ;; Within each fork, possibly, look at properties of 'more' and go further along the lines in comment above.
)

Now, this is quite a common scenario you'd want to handle - and you dislike repeating yourself.
And you hate the extra parenthesis and indentations and conditionals.
Protocols will make the common, simple use-case of hinging on properties of 'arg1' straightforward so your
code isn't littered with conditionals.
multi-methods will go one step ahead and look at all of arg1 and more.

My 2 paise - even in light of the dropping rupee.

--
jaju

Saager Mhatre

unread,
Aug 30, 2013, 2:56:35 AM8/30/13
to clj-...@googlegroups.com

On Friday, August 30, 2013 10:24:53 AM UTC+5:30, Ravindra Jaju wrote:
Nice discussion, guys.

I tend to think of protocols/multi-methods this way - to put it really crudely - since
it helps me overcome the first hurdle in understanding.

;; Presenting foo - the super-dude function which is expected to handle many types of 'arguments.'
(defn foo [arg1 & more]
  ;; fork execution logic based on some properties of arg1
  ;; Within each fork, possibly, look at properties of 'more' and go further along the lines in comment above.

;; Well, multimethods look at the 'properties' (although I don't know if that's the right term but I couldn't come up with a better on either) of all, some or none of the arguments and that determination is entirely atomic and not sequential as your comment above seems to indicate. What I mean is that the 'fork execution logic' is entirely encapsulated within the dispatch function.
 
)

Now, this is quite a common scenario you'd want to handle - and you dislike repeating yourself.
And you hate the extra parenthesis and indentations and conditionals.
 
Protocols will make the common, simple use-case of hinging on properties of 'arg1' straightforward so your
code isn't littered with conditionals.

More specifically, hinging on the class of 'arg1', i.e. the result of (class arg1).
 
multi-methods will go one step ahead and look at all of arg1 and more.

All, some or none of the args, as I mentioned above.
 
My 2 paise - even in light of the dropping rupee.

My 2c, hedging on the rising Dollar ;)
 
--
jaju

- d 

Saager Mhatre

unread,
Aug 30, 2013, 3:36:59 AM8/30/13
to clj-...@googlegroups.com
On Thursday, August 29, 2013 9:42:18 AM UTC+5:30, Sidhant Godiwala wrote:
Hi Saager,

What protocols let you do is dynamically add interfaces to types.

In this case, perimeter would be a function in another protocol

(defprotocol ClosedShape
  (perimeter [this]))

Sidhant/BG,
Well, I got that far. I ended up with this:

(ns expression-problem.more-geom-shape
  (:require [expression_problem.shape])
  (:import [expression_problem.shape Shape Square Circle]))

(defprotocol Perimeter
  (perimeter [x] "length of shape boundary"))

(extend-protocol Perimeter

  Square
  (perimeter [this] (* 4 (.side this)))

  Circle
  (perimeter [this] (* 2 Math/PI (.radius this))))

(deftype Triangle [base height]

  Shape
  (area [this] (/ (* base height) 2))
  
  Perimeter
  (perimeter [this]
    (throw
      (UnsupportedOperationException. "cannot compute perimeter of Triangle with just breadth and height"))))

But, that tears the Shape abstraction into two- Shape and Perimeter[*]. So, if I get this right if someone gets the original protocol wrong, it stays wrong forever, or at least till the library author fixes it.

I acknowledge the point about being able to extend existing types with new protocols. That is an interesting intersection when comparing to most modern dynamic[**] OO languages.

What I'm getting is that, while Clojure doesn't entirely sidestep the Expression Problem, it makes it more manageable by separating specification and implementation. I guess that's a pretty good design trade-off. I would love any references as to why the Clojure team decided on this approach to understand their specific motivations. BG, do you have any links handy?

Note that I'm specifically not mentioning the namespace scoping because, while a very interesting feature, it is somewhat orthogonal to the Expression Problem.

Thanks
- d

PS- Sidhant, I don't know if you tried running the code you put up, but fu.shape/Square didn't work for me. I figure that's cause types end up in the import hierarchy and not the require hierarchy. Just saying...

* Sidhant, I like the name ClosedShape, but then it makes the area function feel like it's not part of closed shapes, which... is about to make my head explode! :S
** dynamically typed as well as dynamically modifiable

Saager Mhatre

unread,
Aug 30, 2013, 3:46:27 AM8/30/13
to clj-...@googlegroups.com
On Friday, August 30, 2013 1:06:59 PM UTC+5:30, Saager Mhatre wrote:
(ns expression-problem.more-geom-shape
  (:require [expression_problem.shape])
  (:import [expression_problem.shape Shape Square Circle]))

Dammit! That should read...

(ns expression-problem.more-geom-shape
  (:require [expression-problem.shape])
  (:import [expression_problem.shape Shape Square Circle]))

- d

Ravindra Jaju

unread,
Aug 30, 2013, 4:40:03 AM8/30/13
to clj-...@googlegroups.com
To me, it sounded the same. 2 more c need to be spent. ;-)


--
Reply all
Reply to author
Forward
0 new messages