This would work:
(new Cow).eat(Left[Grass, Rice](new Grass))
but you have another problem - you defined your abstract class Animal
to expect a type FoodType
which is a subtype of Food
. Types Grass
and Rice
are both individually valid subtypes of Food
, but Either[Grass, Rice]
is not.
A bit of underlying theory:
When you work with a type that takes one out of several possible forms, that's called a sum type. As opposed to a product type, which combines all of the given types into one entity (e.g. Person consists of string first name, string last name, integer age etc.), a sum type only takes one materialization out of all the possible ones. This is what you have - your FoodType is either Grass or Rice or Fish.
Your problem here is that you're approaching your sum type with two different constructs which both serve the same purpose of modelling sum types. One approach is having a trait or an abstract class which is then extended by all the possible options:
trait Food
class Grass extends Food
class Rice extends Food
class Fish extends Food
Another approach is using an out-of-the-box sum type such as Either
. Clumsy thing with Either
is the fact that it only takes two possibilities, so for three you would have to have e.g. Either[Grass, Either[Rice, Fish]]
. In some common Scala libraries such as scalaz or cats there are other, more suitable constructs for sum types (also known as "coproducts"), but let's not go into that now.
So, what you need to do is decide whether you want to stick to subtyping or you want to go with Either. For your use case subtyping is completely fine, so just remove the Either and implement type FoodType
as e.g. Grass
and it will work, as you noted yourself in the comment on the same line.
BTW your Food
is a class, but notice how I said "trait or an abstract class". This is the best practice principle; if you're not expecting to ever need an instance of Food
itself via new Food
(and you're not; you're only going to instantiate its subclasses, e.g. new Grass
), then it's better to not allow such instantiation in the first place.
Another hint is to make such trait / abstract class sealed
and the subtypes final case class
, which means that nobody else can ever provide extra options (that is, introduce some own custom food):
sealed trait Food
final case class Grass extends Food
final case class Rice extends Food
final case class Fish extends Food
Case class (as opposed to standard class) server the purpose of defining some stuff for you out of the box, such as
- methods like equals(), copy() etc.
- support for pattern matching (by implementing apply/unapply for you)
- default companion object, which allows you to use
Grass()
instead of new Grass()
- etc.
But OK I'm diverging :) hopefully this helps.
EDIT:
OK, now I realised your actual problem. You need to introduce another sum type. You already have Food
, but now you need "cow food". You can easily model it exactly like that, adding a CowFood
trait that extends Food
and is extended by Grass
and Rice
.
sealed trait Food
sealed trait CowFood extends Food
sealed trait HorseFood extends Food
sealed trait SealFood extends Food
final case class Grass() extends CowFood with HorseFood
final case class Rice() extends CowFood
final case class Fish() extends SealFood
...
type FoodType = CowFood
(remember that traits are stackable; grass is both cow food and horse food)
I'm not a huge fan of subtyping, but for this particular problem it's a cleaner solution than getting entangled in Eithers and mapping all around the place.