I'm using Scala 2.10.4.
Please bare with the analogy - the actual code is deeply embedded in a complicated program, so rather than explain that, I’ll abstract the problem in a time honoured way to talk about Animals ;-)
In scala I have 2 traits - for example:
Animal, and HouseBase.
I have no access to change Animal, but I inherit from it with classes like Dog, Rabbit, Fish. Annoyingly I can’t change every subclass as I don’t own all the subclasses I use.
My animals all live somewhere - their homes must inherit from HouseBase. I can change HouseBase and it’s subclasses (via another layer of abstraction, if I must).
So a Dog is a subclass of Animal, and would live in a Kennel which is a subclass of HouseBase.
A Rabbit would live in a Hutch, and a Fish in a Tank.
Note there is not a 1:1 relationship enforced here - a Fish could also live in a Pond, and we’d have to be able to handle that too.
What I’d hoped was that -- given a concrete animal (eg Fish), that is referenced via an abstract type Animal, and given a concrete return type (eg Tank), Scala would be able to automagically pick the correct implicit parameter in the design I have below.
object AnimalSelectionProblem extends App {
def abstractFish : Animal = new Fish(true, 20.0)
def concreteFish : Fish = new Fish(false, 30.0)
def abstractDog : Animal = new Dog("tasty bone")
def concreteDog : Dog = new Dog("yummy bone")
def abstractRabbit : Animal = new Rabbit(5)
def concreteRabbit : Rabbit = new Rabbit(10)
import HouseImplicits._
val myTank1: Tank = HouseImplicits.create(abstractFish)
val myTank2: Tank = HouseImplicits.create(concreteFish)
val myKennel1: Kennel = HouseImplicits.create(abstractDog)
val myKennel2: Kennel = HouseImplicits.create(concreteDog) // This works
val myhutch1: Hutch = HouseImplicits.create(abstractRabbit)
val myhutch2: Hutch = HouseImplicits.create(concreteRabbit) // This works
}
However there are 2 related problems.
Problem 1 - If the animal is referenced as an abstract, then the implicit parameter will only look for functions that take the abstract type (Animal) rather than the underlying concrete type. I suspect the solution may be to use ClassTags, because Scala doesn’t seem to use runtime time information? I had a go at implementing this and got hopelessly l lost (I’m fairly new to Scala!).
Problem 2 - If my animal can live in more than one type of House then a similar issue occurs, that even if a concrete return type is specified, the compiler will find the 2 implicit objects for Fish ambiguous. I’m a bit stumped about what to do here!
I can dream up solutions with manual boilerplate to match the type at runtime, but this isn’t very extensible.
Any ideas gratefully received! Rest of the code is below.
Edit - these links seems to confirm what I had suspected. That compile time polymorphism is used and hence the runtime type cannot be known:
http://like-a-boss.net/2013/03/29/polymorphism-and-typeclasses-in-scala.html
So, I guess my question now is, given this, is there a way to modify my example to use runtime dispatch?
Animals:
trait Animal {
}
class Dog(val boneName: String) extends Animal
class Rabbit(val length: Int) extends Animal
class Fish(val likesFrogs: Boolean, val optimumTemp: Double) extends Animal
Houses and Implicits:
sealed trait HouseBase
// Made up some arbitrary member variables
case class Kennel(posessions: Seq[String]) extends HouseBase
case class Hutch(length: Int) extends HouseBase
case class Tank(waterTemp: Double) extends HouseBase
case class Pond(containsFrog: Boolean) extends HouseBase
sealed trait HouseCreator[A <: Animal, HB <: HouseBase] {
def create(animal: A): HB
}
object HouseImplicits {
implicit object BuildKennelForDog extends HouseCreator[Dog, Kennel] {
override def create(dog: Dog): Kennel = {
new Kennel(Seq(dog.boneName))
}
}
implicit object BuildTankForFish extends HouseCreator[Fish, Tank] {
override def create(fish: Fish): Tank = {
new Tank(fish.optimumTemp)
}
}
implicit object BuildPondForFish extends HouseCreator[Fish, Pond] {
override def create(fish: Fish): Pond = {
new Pond(fish.likesFrogs)
}
}
implicit object BuildHutchForRabbit extends HouseCreator[Rabbit, Hutch] {
override def create(rabbit: Rabbit): Hutch = {
new Hutch(rabbit.length*5)
}
}
def create[A <: Animal, H <: HouseBase](animal: A)(implicit house: HouseCreator[A,H]) : H = {
val newHouse = house.create(animal)
newHouse
}
}