Reputation: 5392
sorry for the long winded question:
Say I have a list of animals and I want to split them up like so:
BasicAnimal = {Cat, Dog}
Carnivore = {Cat, Dog, Dragon}
Herbivore = {Cat, Dog, Horse}
Now, these animals also have to live somewhere. So there's a
BasicShelter with a method shelter(animal: BasicAnimal)
Den with a method shelter(animal: Carnivore)
Shed with a method shelter(animal: Herbivore)
What's the best way to implement this in Scala? One attempt was:
class BasicAnimal extends Enumeration{
val Cat, Dog = Value
}
class Carnivore extends BasicAnimal{
val Dragon = Value
}
class Herbivore extends BasicAnimal{
val Horse = Value
}
and then
class BasicHouse{
def shelter(animal: BasicAnimal) = {//lots of code}
}
class Den{
def shelter(animal: Carnivore) = {
//again lots of code, but the cases dealing with Cat and Dog can be relegated to super.shelter
}
}
class Shed{
//the same
}
Sadly, this won't work. The Dog from Carnivore is different from the Dog in BasicAnimal. That is Carnivore.Dog == BasicAnimal.Dog returns false, so, the only way to reuse the code from BasicHouse in Den is by having a rather hacky equality method that compares the strings of the enums (or something similar). It works, but it's very unclean. Can you see any other possibilities?
Upvotes: 9
Views: 493
Reputation: 49695
As per @paradigmatic's answer, but with a few enhancements:
sealed abstract trait BasicAnimal
sealed abstract trait Carnivore extends BasicAnimal
sealed abstract trait Herbivore extends BasicAnimal
sealed abstract trait Omnivore extends Carnivore with Herbivore
case object Dog extends Omnivore
case object Cat extends Omnivore
case object Dragon extends Carnivore
case object Horse extends Herbivore
Making the traits abstract allows you to take advantage of completeness checking in pattern matches (which would otherwise warn that a match on the trait itself wasn't attempted)
The Omnivore
traits removes some duplication, and also helps with pattern matching
Using extends
instead of self typing is just plain cleaner, and more intuitive
case object
instead of object
is mostly done to better document the intent of the code, and also to provide a sane implementation of toString
Upvotes: 9
Reputation: 40461
An alternative solution to enumeration is to use sealed trait
to define enumerations.
With your example:
sealed trait BasicAnimal
sealed trait Carnivore { self: BasicAnimal => }
sealed trait Herbivore { self: BasicAnimal => }
object Dog extends BasicAnimal with Carnivore with Herbivore
object Cat extends BasicAnimal with Carnivore with Herbivore
object Dragon extends BasicAnimal with Carnivore
object Horse extends BasicAnimal with Herbivore
That is more flexible than Enumeration, but you lose the possibility of easily enumerating all values.
Upvotes: 5