7

In Clean Code by Uncle Bob, page 124-125 he states

Procedural code (code using data structures) makes it easy to add new functions without changing the existing data structures. OO code, on the other hand, makes it easy to add new classes without changing existing functions.

I'm reading about Multiple Dispatches like in this article about multiple dispatch and wondering if it solves the problem.

Jörg W Mittag says that this has been termed as the Expression Problem by Phil Wadler, but he states that to solve the problem we should

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 [...]

I'm more interested in the extensibility of a software, i.e. if it's "easy" to add new functionalities incrementally. I have an intuitive definition of "easy" in this case, and it includes bug resistance and no duplicate code.


Here is Uncle Bob examples from the book

Polymorphic Shapes

public class Square implements Shape {
 private Point topLeft;
 private double side;

 public double area() {
  return side * side;
 }
}

public class Rectangle implements Shape {
 private Point topLeft;
 private double height;
 private double width;

 public double area() {
  return height * width;
 }
}

public class Circle implements Shape {
 private Point center;
 private double radius;
 public final double PI = 3.141592653589793;

 public double area() {
  return PI * radius * radius;
 }
}

Procedural Shape

public class Square {
 public Point topLeft;
 public double side;
}

public class Rectangle {
 public Point topLeft;
 public double height;
 public double width;
}

public class Circle {
 public Point center;
 public double radius;
}

public class Geometry {
 public final double PI = 3.141592653589793;

 public double area(Object shape) throws NoSuchShapeException {
  if (shape instanceof Square) {
   Square s = (Square) shape;
   return s.side * s.side;
  } 

  else if (shape instanceof Rectangle) {
   Rectangle r = (Rectangle) shape;
   return r.height * r.width;
  } 
  else if (shape instanceof Circle) {
   Circle c = (Circle) shape;
   return PI * c.radius * c.radius;
  }
  throw new NoSuchShapeException();
 }
}
Jp_
  • 189
  • 5
  • Jörg W Mittag points out something interesting here, the "problem" Uncle Bob says in this case that is "difficult" to add functions or classes depending how you choose to start. But why is it difficult? Because you have to recompile something you didn't change? Because you have to write duplicate code? – Jp_ May 02 '18 at 09:48

1 Answers1

9

This has been termed the Expression Problem by Phil Wadler, although it is much older than the discussion in which he came up with this term. Solving it is one of the "holy grails" of Programming Language Design.

One of the problems with something that is so famous is that everybody comes up with their own definitions, so talking about it, without rigorously defining it, makes little sense, except to invite flamewars.

Phil Wadler had these four constraints:

  1. Extend the code with new operations on the existing kinds of data.
  2. Extend the code with new kinds of data that work with the existing operations.
  3. This should be true extension, i.e. no modification of existing code.
  4. It should be statically type-safe.

Ruby-style mutable Open Classes with Monkeypatching or ECMAScript-style mutable Prototypes solve problems 1 and 2. We can argue about whether they really solve point 3, though: If you mutate a class in memory, but the code that performs this mutation is in a separate file … is that extension or modification of existing code?

So, let's say, they solve 2.5 of the problems, but they are obviously not statically type-safe.

CLOS-style multimethods solve 3 fully, but still fail at 4. I think both MultiJava and Tuple Class-style multimethods might qualify, but I am not too familiar with them.

Haskell's Typeclasses were the first system to satisfy all 4 constraints.

Martin Odersky et al. added another two constraints:

  1. It should be modular, i.e. the extension should be in a separate module (in the PLT sense of the word) that is separately deployed.
  2. And this includes modular typechecking.

Haskell Typeclasses actually fail at #6. Scala implicit values and implicit conversions, however, do solve all 6 problems.

Note: there may be other features in Haskell that solve all 6, but I am not too familiar with all the extensions and research that have been done since Typeclasses.

To answer your actual question:

I'm reading about Multiple Dispatches like in this article and wondering if it solves the problem

Whether or not Multiple Dispatch solves the problem depends on how yo define "solve" and what you consider "the problem".

Jörg W Mittag
  • 101,921
  • 24
  • 218
  • 318
  • Thank you for the directions. I found this text from Philip Wadler http://homepages.inf.ed.ac.uk/wadler/papers/expression/expression.txt where he gives the Visitor Pattern as a solution. I didn't find the 4 constraints you mention, do you have another source? Also reading about the Visitor Pattern in Wikipedia I could see how Multiple Dispatch makes its implementation much easier (see the C# example). – Jp_ Apr 28 '18 at 23:32
  • Regarding to my question you are right, I didn't define it very well. I'm envisioning to maximize extensibility and minimize boilerplate, I'm most interested in C# and JavaScript. – Jp_ Apr 28 '18 at 23:57
  • So I guess I wouldn't mind losing the statically typechecking. – Jp_ Apr 29 '18 at 00:03
  • Do you have any resource that talks about how Scala solves expression problem? I can find individual features, but I don't know how those can be applied to solve expression problem. – Euphoric May 02 '18 at 19:27