3

This question already has an answer here:

I am reading Martin Fowler's 'Refactoring: Improving the Design of Existing Code'. I have not understood a section of the second chapter where Kent Beck describes the pros of indirection.

One of the pros listed is Encoding Conditional Logic and it is described as:

Objects have a fabulous mechanism, polymorphic messages, to flexibly but clearly express conditional logic. By changing explicit conditionals to messages, you can often reduce duplication, add clarity, and increase flexibility all at the same time.

What are polymorphic messages? What does he mean by 'change explicit conditionals to messages'?

marked as duplicate by Bart van Ingen Schenau, amon, user22815, MichelHenrich, gnat Feb 16 '16 at 7:28

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 4
    what is message passing in OO? – gnat Jan 26 '16 at 11:33
  • 1
    You might know them as virtual methods. – CodesInChaos Jan 26 '16 at 15:36
  • @gnat I don't see any mention of polymorphic messages in the answers there. – Ixrec Jan 27 '16 at 0:09
  • 1
    @Ixrec per my reading, asker appears to miss whole concept of how message passing relates to OO – gnat Jan 27 '16 at 0:13
2

Another term would be "dynamic dispatch". Below is an example using (somewhat non-idiomatic) C#.

Say we have the following list type:

public abstract class List<T> {
    public abstract bool isEmpty();
    public abstract T head();
    public abstract List<T> tail();
}

class Empty<T> : List<T> {
    public Empty() { }
    public override bool isEmpty() { return true; }
    public override T head() { 
        throw new ApplicationException("head of empty list"); 
    }
    public override List<T> tail() { 
        throw new ApplicationException("tail of empty list"); 
    }
}

class Cons<T> : List<T> {
    private T head;
    private List<T> tail;
    public Cons(T head, List<T> tail) {
        this.head = head;
        this.tail = tail;
    }
    public override bool isEmpty() { return false; }
    public override T head() { return head;  }
    public override List<T> tail() { return tail; }
}

You could implement a length function as follows:

int length(List<T> xs) {
    if(xs.isEmpty()) { 
        return 0; 
    } else {
        return 1 + length(xs.tail());
    }
}

Alternatively, using dynamic dispatch as Kent Beck suggests, we could eliminate the conditional by adding length as a method of List<T>.

public abstract class List<T> {
    /* ... */
    public abstract int length();
}

class Empty<T> : List<T> {
    /* ... */
    public override int length() {
        return 0;
    }
}

class Cons<T> : List<T> {
    /* ... */
    public override int length() {
         return 1 + tail.length();
    }
}

This increases flexibility because we could now add another class with it's own implementation of length that can be smarter than our length function.

class Concat<T> : List<T> {
    private List<T> left, right;
    public Concat(List<T> left, List<T> right) {
        this.left = left;
        this.right = right;
    }
    /* ... */
    public override int length() {
        return left.length() + right.length();
    }
}

As a bit of an aside, there are problems with the definition of length in Cons<T> that will cause it to use a lot of stack space and likely produce a stack overflow in real use. The problem is that it isn't written in a tail recursive form. That's easily corrected, but even if corrected, since virtually no OO language guarantees tail call elimination the benefits that Kent Beck mentions can't be achieved in this example. In other words, the lack of tail call elimination significantly hampers one of the main benefits of OO style.

  • Consider placing <!-- language-all: lang-cs --> at the top of your post to enable syntax highlighting. See the help for more information. – 5gon12eder Jan 26 '16 at 12:56
  • 1
    The last paragraph cannot be emphasized enough. As Guy Steele has already pointed out in the 70s, it is simply impossible to write object-oriented code in a language without proper tail calls, it's a shame that the only mainstream OO language with proper tails calls is ECMAScript. – Jörg W Mittag Jan 26 '16 at 13:17
1

When you send a message to an object, the object is free to respond to the message however it wishes. In particular, different objects may respond to the same message differently: that's the essence of ad-hoc polymorphism.

Doing different things is of course also what conditionals are all about. So, polymorphic messages are a form of conditional, and as it turns out a very powerful one. Here's an example implementing traditional conditionals (i.e. if/then/else) using polymorphic messages in Ruby, the gist of it boils down to this:

class TrueClass
  def iff(thn: ->{}, els: ->{})
    thn.()
  end
end

class FalseClass
  def iff(thn: ->{}, els: ->{})
    els.()
  end
end

(2 < 3).iff(thn: -> { puts '2 is less than 3' }, els: -> { puts '2 is greater than 3' })
# 2 is less than 3

And a slightly more complete example in Scala:

sealed abstract trait Buul {
  def apply[T, U <: T, V <: T](thn: => U)(els: => V): T
  def &&&(other: => Buul): Buul
  def |||(other: => Buul): Buul
  def ntt: Buul
}

case object Tru extends Buul {
  override def apply[T, U <: T, V <: T](thn: => U)(els: => V): U = thn
  override def &&&(other: => Buul) = other
  override def |||(other: => Buul): this.type = this
  override def ntt = Fls
}

case object Fls extends Buul {
  override def apply[T, U <: T, V <: T](thn: => U)(els: => V): V = els
  override def &&&(other: => Buul): this.type = this
  override def |||(other: => Buul) = other
  override def ntt = Tru
}

object BuulExtension {
  import scala.language.implicitConversions
  implicit def boolean2Buul(b: => Boolean) = if (b) Tru else Fls
}

import BuulExtension._

(2 < 3) { println("2 is less than 3") } { println("2 is greater than 3") }
// 2 is less than 3

You will notice that this object-oriented encoding of conditionals and booleans is exactly the same as the Church Encoding of conditionals and booleans in the λ-calculus. That's no coincidence: OO is about behavioral abstraction, and the λ-calculus supports nothing but function (i.e. behavior) abstraction, prompting Cook to call it "the first OO language", only half joking.

Not the answer you're looking for? Browse other questions tagged or ask your own question.