Couple of weeks back I presented a introductory session on Destructuring and Pattern Matching at Functional Conference 2018, Bengaluru where I demoed live how Destructuring is different from Pattern Matching by using various Functional Programming languages and other non-FP languages.

Difference Between Destructuring & Pattern Matching

Starting with dynamically typed languages like JavaScript

 
// Destructuring gives us a short-hand way of naming parts of the data-structure.
const list = [1,2,3,4,5,6]
let [first, second, ...rest] = list
console.info("first = ", first);
console.info("second = ", second);
console.info("rest = ", rest);

// Destructuring Object
const name = {first: "Dhaval", last: "Dalal", salutation: 'Mr.'};
const {first: firstName, last: lastName} = name;
console.info(firstName);
console.info(lastName);

// Destructuring Function params
function capitalize({first: firstName, last: lastName}){
  return firstName.toUpperCase() + " " + lastName.toUpperCase();
}
console.info(capitalize(name));

// Destructuring Function params
function distance([x1,y1], [x2,y2]){
  return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}

console.info(distance([0,0], [0,0]))
console.info(distance([3,0], [0,0]))
console.info(distance([0,0], [0,4]))
console.info(distance([3,0], [0,4]))

This is Destructuring. It gives you a short-hand way of naming and getting the innards of the structure. At present (at the time of writing this post), JavaScript does not have Pattern Matching out-of-the-box. So, leaving JavaScript there, I moved to Erlang and then hopped over to statically typed languages like Haskell where I demoed the difference between Destructuring and Pattern Matching.

In Pattern matching, we are not only matching on relative positions of elements in the data-structure, but also trying to dispatch to different definitions (look at the distance function – it has 2 definitions) based on the value inside the data structure. This dispatch is a kind of conditional construct, while in destructuring there is nothing conditional about it.

import Data.Char (toUpper)

data Name = Name { 
  first :: String, 
  last :: String,
  salutation :: String
} deriving (Show)

capitalize :: Name -> String
capitalize (Name fName lName _) = (upper fName) ++ " " ++ (upper lName)
  where
    upper cs = [toUpper c | c  Double -> Double
pythagorean x y = sqrt (square x + square y)
  where
    square n = n ^^ 2

distance :: (Double, Double) -> (Double, Double) -> Double
distance (0,0) (0,0) = 0
distance p1@(x1,y1) p2@(x2,y2)
  | p1 == (0,0) || p2 == (0,0) = (let (x,y) = if p1 == (0,0) then p2 else p1 in pythagorean x y)
  | otherwise = pythagorean (x2 - x1) (y2 - y1)

main :: IO ()
main = do
  let name = Name "Dhaval" "Dalal" "Mr."
  print $ name
  print $ capitalize name
  print $ distance (0,0) (0,0)
  print $ distance (3,0) (0,0)
  print $ distance (0,0) (0,4)
  print $ distance (3,0) (0,4)
  print "DONE"

and finally closing with the same example rendered in Clojure. If this interests you, here is the link for examples in other languages https://github.com/DhavalDalal/destructuring-and-pattern-matching/tree/master/01_languages

Double Dispatch

After that, I demoed the implementation of Double-Dispatch using famous Paper-Scissor-Rock game, starting with Java. The same was then implemented in the above mentioned languages.

; We’ll use record to represent the three choices.
(defrecord Paper [])
(defrecord Scissor [])
(defrecord Rock [])

; Multi-methods can be used to solve this problem
; Here is our dispatch function
(defmulti beats
  (fn [e1 e2] [(class e1) (class e2)]))

(defmethod beats [Paper   Scissor] [e1 e2] "paper loses")
(defmethod beats [Paper   Rock]    [e1 e2] "paper wins")
(defmethod beats [Scissor Paper]   [e1 e2] "scissor wins")
(defmethod beats [Scissor Rock]    [e1 e2] "scissor loses")
(defmethod beats [Rock    Paper]   [e1 e2] "rock loses")
(defmethod beats [Rock    Scissor] [e1 e2] "rock wins")
(defmethod beats :default          [e1 e2] "draw")

(println "paper beats paper = "     (beats (Paper.)   (Paper.)))
(println "paper beats scissor = "   (beats (Paper.)   (Scissor.)))
(println "paper beats rock = "      (beats (Paper.)   (Rock.)))
(println "scissor beats paper = "   (beats (Scissor.) (Paper.)))
(println "scissor beats scissor = " (beats (Scissor.) (Scissor.)))
(println "scissor beats rock = "    (beats (Scissor.) (Rock.)))
(println "rock beats paper = "      (beats (Rock.)    (Paper.)))
(println "rock beats scissor = "    (beats (Rock.)    (Scissor.)))
(println "rock beats rock = "       (beats (Rock.)    (Rock.)))

Below is the link to implementations in Java, Scala, Haskell and Erlang
https://github.com/DhavalDalal/destructuring-and-pattern-matching/tree/master/02_double_dispatch

Subsuming The Visitor Pattern

The session concluded by showing how the (in)famous Visitor Pattern that is usually implemented in languages like Java, C# and C++. Here is the problem statement: How will you enhance this code to include rendering these objects on following platforms?

  • OpenGL
  • SVG

                              +---------------+
	                          |    Shape3d    |
	                          +---------------+
	                          | surfaceArea() |<-------------------------------+
	                          | volume()      |                                |
	                          +---------------+                                |
	                                  ^                                        |
	                                 / \                                       |
	                                  |                                        |
	         +------------------------+-----------------------+                |
	         |                        |                       |                |
	  +---------------+       +---------------+       +---------------+        |
	  |   Cylinder    |       |     Sphere    |       |CompositeShape |        |
	  +---------------+       +---------------+       +---------------+ *      |
	  | surfaceArea() |       | surfaceArea() |       | surfaceArea() |--------+
	  | volume()      |       | volume()      |       | volume()      |
	  +---------------+       +---------------+       +---------------+

Possible Solutions:

  • Solution 1:
    • Introduce renderOpenGL() and renderSVG() methods in Shape3d.
    • We have now made shapes aware of the “drawing platform” specific code. Though they are renderable objects, but how to render would be present inside them.
    • It does not scale to more rendering platforms. We have to add to hierarchy everytime.
  • Solution 2: Use Visitor Pattern
	                      +-----------------+                                    +-----------------+
	                      |     Shape3d     |----------------------------------->|     Visitor     |
	                      +-----------------+                                    +-----------------+
	                      | surfaceArea()   |                                    | visit(Cylinder) |
	                      | volume()        |<-----------------------+           | visit(Sphere)   |
	                      | accept(Visitor) |                        |           +-----------------+   
	                      +-----------------+                        |                    ^    
	                              ^                                  |                   / \
	                             / \                                 |                    |
	                              |                                  |            +--------------------+
	         +--------------------+----------------------+           |            |                    |     
	         |                    |                      |           |   +-----------------+  +-----------------+   
	 +-----------------+  +-----------------+  +-----------------+   |   |  OpenGLVisitor  |  |    SVGVisitor   |   
	 |     Cylinder    |  |      Sphere     |  |  CompositeShape |   |   +-----------------+  +-----------------+   
	 +-----------------+  +-----------------+  +-----------------+ * |   | visit(Cylinder) |  | visit(Cylinder) |   
	 | surfaceArea()   |  | surfaceArea()   |  | surfaceArea()   |---+   | visit(Sphere)   |  | visit(Sphere)   |   
	 | volume()        |  | volume()        |  | volume()        |       +-----------------+  +-----------------+   
	 | accept(Visitor) |  | accept(Visitor) |  | accept(Visitor) |                                               
	 +-----------------+  +-----------------+  +-----------------+ 

Subsumption of this pattern was then demoed in Scala, Haskell, Erlang and Clojure. All these languages provide the facility of Destructuring and Pattern Matching. Below is a Subsumed Visitor implementation in Erlang.

-record (cylinder, {baseRadius = 0, height = 0}).
-record (sphere, {radius = 0}).

volume(#cylinder {baseRadius = BaseRadius, height = Height}) ->
	math:pi() * math:pow(BaseRadius, 2) * Height;
volume(#sphere {radius = Radius}) ->
	4.0 / 3.0 * math:pi() * math:pow(Radius, 3);
volume(List = [_|_]) -> 
  lists:foldl(fun(Shape, Acc) -> Acc + volume(Shape) end, 0, List);
volume([]) -> 0;
volume(_) -> not_ok.

surfaceArea(#cylinder {baseRadius = BaseRadius, height = Height}) ->
  BaseArea = math:pi() * math:pow(BaseRadius, 2),
  BaseCircumference = 2 * math:pi() * BaseRadius,
  2 * BaseArea + BaseCircumference * Height;
surfaceArea(#sphere {radius = Radius}) ->
  4.0 * math:pi() * math:pow(Radius, 2);
surfaceArea(List = [_|_]) -> 
  lists:foldl(fun(Shape, Acc) -> Acc + surfaceArea(Shape) end, 0, List);
surfaceArea([]) -> 0;
surfaceArea(_) -> not_ok.

render(openGL, #cylinder {}) ->
  io:format("~p~n", ["OpenGL: rendering cylinder"]);
render(openGL, #sphere {}) ->
  io:format("~p~n", ["OpenGL: rendering sphere"]);
render(svg, #cylinder {}) ->
  io:format("~p~n", ["SVG: rendering cylinder"]);
render(svg, #sphere {}) ->
  io:format("~p~n", ["SVG: rendering sphere"]);
render(Platform, List = [_|_]) -> 
  io:format("Rendering Composite...~n"),
  lists:foreach(fun(Shape) -> render(Platform, Shape) end, List);
render(_, _) -> not_ok.

main([]) ->
  Cylinder = #cylinder {baseRadius = 10, height = 10},
  Sphere = #sphere {radius = 10},
  Composite = [Cylinder, Sphere],
  io:format("Volume of Cylinder ~p~n", [volume(Cylinder)]),
  io:format("Volume of Sphere ~p~n", [volume(Sphere)]),
  io:format("Volume of Composite ~p~n", [volume(Composite)]),
  % io:format("Volume of 2 ~p~n", [volume(2)]),
  io:format("Surface Area of Cylinder ~p~n", [surfaceArea(Cylinder)]),
  io:format("Surface Area of Sphere ~p~n", [surfaceArea(Sphere)]),
  io:format("Surface Area of Composite ~p~n", [surfaceArea(Composite)]),
  % io:format("Surface Area of 2 ~p~n", [surfaceArea(2)]),
  io:format("~p~n", [render(openGL, Composite)]),
  io:format("~p~n", [render(svg, Composite)]),
  io:format("DONE").

Here is the link to source code https://github.com/DhavalDalal/destructuring-and-pattern-matching/tree/master/03_visitor

Conclusion

  1. Most functional programming languages provide some level of de-structuring. Destructuring gives you a short-hand way of naming and getting the innards of the structure.

  2. The difference between destructuring and pattern matching is that in Pattern matching, we are not only matching on relative positions of elements in the data-structure, but also trying to dispatch to different definitions based on the value inside the data structure. This dispatch is a kind of conditional construct, while in destructuring there is nothing conditional about it. So, Pattern matching is about associating a definition with a particular set of inputs. So, for the same function, we can have many such definitions for inputs that we destructure on.


Note: This is an elaboration of one of the melodies that Ryan Lemmer and myself prepared for the Code Jugalbandi. You can download this Jugalbandi – it appeared in the Healthy Code Magazine in April 2015.

Advertisements