Reputation: 987
I am trying to pass either Grass
or Rice
object. But, it fails in compiling. I tried below Either[]
option based on this link using Either. But, its not working.
I want to restrict passing Fish Object like this. I want to pass only Rice
or Grass
.
(new Cow).eat(new Fish) // I don't want this to happen
Please let me know why Either
is not working here.
object AbstractType {
def main(args: Array[String]): Unit = {
(new Cow).eat(new Grass) // Error here -- type mismatch; found : Grass required: scala.util.Either[Grass,Rice]
}
}
abstract class Animal{
type FoodType <: Food
def eat(food : FoodType)
}
class Food{}
class Grass extends Food
class Rice extends Food
class Fish extends Food{}
class Cow extends Animal{
type FoodType = Either[Grass,Rice] // instead of Either[], keeping Grass here is compiling successfully as expected.
def eat(food : FoodType) {
println("Cow eats")
}
}
I tried below approach as suggested by slouc
. But, even this approach not able to restrict this. (new Cow).eat(Fish())
.
object AbstractType {
def main(args: Array[String]): Unit = {
(new Cow).eat(Grass())
}
}
abstract class Animal{
type FoodType <: Food
def eat(food : FoodType)
}
sealed trait Food
final case class Grass() extends Food
final case class Rice() extends Food
final case class Fish() extends Food
class Cow extends Animal{
type FoodType = //fill here
def eat(food : FoodType) {
println("Cow eats")
}
}
My Question : what will be the better approach to fill in the above code so that, I can pass only Rice
or Grass
object.(if not with either , how to achieve other way) and restrict Fish
object.
Upvotes: 1
Views: 197
Reputation: 9698
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
Grass()
instead of new Grass()
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.
Upvotes: 3
Reputation: 15770
In the code above, FoodType is defined twice, and it's definition in Cow
shadows the one in Animal
- this are two different types. You don't need Either
in this case. You can define eat
method with parameter of type Food
, and just pass Grass
, Rice
or Fish
, as all this classes inherit from Food
The example does not compile, because it expects parameter of type Either[Grass, Rice]
, but parameter of type Grass
is passed.
Upvotes: 0