-
-
Save rentalcustard/4017647 to your computer and use it in GitHub Desktop.
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); | |
} | |
} | |
} |
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.
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...
Since
Speaker
instances come from theSpeakerSource
and can be of any type which implementsSpeaker
, the specific type must be known beforespeak()
can be dispatched. This is trivial with static analysis, ifSpeakerSource#getSpeakers()
is simple:@@@java
public List getSpeakers() {
List speakers = new ArrayList();
speakers.add(new Comedian());
speakers.add(new Dog());
return speakers;
}
However, if the method is getting speakers from elsewhere, something like:
@@@java
public List getSpeakers() {
List speakers = new ArrayList();
List 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.