Skip to content

Instantly share code, notes, and snippets.

@rentalcustard
Created November 5, 2012 15:07
Show Gist options
  • Save rentalcustard/4017647 to your computer and use it in GitHub Desktop.
Save rentalcustard/4017647 to your computer and use it in GitHub Desktop.
Polymorphism => late binding
class Doer {
public void doStuff(Speaker speaker) {
speaker.speak();
}
}
interface Speaker {
void speak();
}
class Program {
public static void main(String... args) {
SpeakerSource someSource = new SpeakerSource();
List<Speaker> speakers = someSource.getSpeakers();
Doer doer = new Doer();
for (Speaker speaker : speakers) {
doer.doStuff(speaker);
}
}
}
@rentalcustard
Copy link
Author

Since Speaker instances come from the SpeakerSource and can be of any type which implements Speaker, the specific type must be known before speak() can be dispatched. This is trivial with static analysis, if SpeakerSource#getSpeakers() is simple:

public List<Speaker> getSpeakers() {
  List<Speaker> speakers = new ArrayList<Speaker>();
  speakers.add(new Comedian());
  speakers.add(new Dog());
  return speakers;
}

However, if the method is getting speakers from elsewhere, something like:

public List<Speaker> getSpeakers() {
  List<Speaker> speakers = new ArrayList<Speaker>();
  List<SpeakerData> fromDb = this.database.getSpeakerData();
  for (SpeakerData row : fromDb) {
    if (row.type == "dog") {
      speakers.add(new Dog(row.name));
    } else {
      speakers.add(new Comedian(row.name));
    }
  }
  return speakers;
}

Then there's no way to determine the types being sent to Doer#doStuff without executing the code. So we need runtime binding here.

@timcowlishaw
Copy link

Right, I could be entirely wrong here, as I'm as confused as you are, so take this with a large pinch of salt. However, from what I understand...

One of the key features of Haskell's typeclass relationship is that the caller gets to specify the concrete type of a value. For instance

number :: Num n => n

-- I can call this in different contexts, as different concrete types:
numberAsInt :: Int
numberAsInt = number

numberAsFloat :: Float
numberAsFloat = number

This is in contrast to the subtype relationship illustrated by Java interfaces, where the callee chooses the concrete type, and the caller depends only on the interface it requires.

So, for instance, this isn't allowed:

data Animal = Animal { greeting :: String }
data Comedian = Comedian { joke :: String}

class Speaker s where
  speak :: s -> String

instance Speaker Animal where
  speak = greeting

instance Speaker Comedian where
  speak = joke

data SpeakerData = SpeakerData { speakerType :: String, catchPhrase :: String }

makeSpeaker :: Speaker s => SpeakerData -> s
makeSpeaker (SpeakerData "Animal" cp) = Animal cp
makeSpeaker (SpeakerData "Comedian" cp) = Comedian cp

...this won't compile, as it's not possible for me to call (makeSpeaker (SpeakerData "Comedian" "Knock Knock!") :: Animal), and get a valid Animal instance out.

So. How then do we replicate the behaviour you're after? We can use something called an existential type:

data AnySpeaker = forall s . Speaker s => AnySpeaker s

instance Speaker AnySpeaker where
speak (AnySpeaker s) = speak s


Then amend the type signature of  makeSpeaker to:

```haskell
makeSpeaker :: SpeakerData -> AnySpeaker

This means that you get a value back that fulfils the contract you want (as AnySpeaker is an instance of Speaker). However, the concrete type is fully specified, which means the caller cannot then refine the type of the value. So, I guess, late binding is required for subtype polymorphism, but not for typeclass polymorphism.

More here - with a link to an article on why the existential type hack above is an antipattern

Sorry this is all a bit vague, but it's pushing the limits of my understanding too...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment