我正在尝试传递Grass
或Rice
对象。但是,它编译失败了。我根据此链接using Either尝试了以下Either[]
选项。但是,它不起作用。
我想像这样限制传递Fish Object。我只想传递Rice
或Grass
。
(new Cow).eat(new Fish) // I don't want this to happen
请告诉我为什么Either
无法在这里工作。
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")
}
}
我按照slouc
的建议尝试了以下方法。但是,即使这种方法也无法限制这一点。 (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")
}
}
我的问题:填写上述代码的更好方法是什么,我只能传递Rice
或Grass
对象。(如果没有,则如何实现其他方式)并限制Fish
对象。
答案 0 :(得分:3)
这样可行:
(new Cow).eat(Left[Grass, Rice](new Grass))
但是您还有另一个问题 - 您定义了抽象类Animal
以期望类型FoodType
是Food
的子类型。类型Grass
和Rice
都是Food
的单独有效子类型,但Either[Grass, Rice]
不是。
一些潜在的理论:
当你使用一种从几种可能的形式中选择一种的类型时,它被称为和类型。与产品类型相反,产品类型将所有给定类型组合成一个实体(例如,Person由字符串名字,字符串姓氏,整数年龄等组成), sum类型< / em>只从所有可能的实现中获取一个实现。这就是你所拥有的 - 你的FoodType是草,米或鱼。
这里的问题是,您正在使用两种不同的结构来接近您的总和类型,这两种结构都用于建模和类型的相同目的。一种方法是使用特征或抽象类,然后通过所有可能的选项进行扩展:
trait Food
class Grass extends Food
class Rice extends Food
class Fish extends Food
另一种方法是使用开箱即用的总和类型,例如Either
。 Either
笨拙的事实是,它只需要两种可能性,所以对于三种你可能必须有这样的可能性。 Either[Grass, Either[Rice, Fish]]
。在一些常见的Scala库中,例如scalaz或cat,还有其他更合适的sum类型构造(也称为“coproducts”),但现在不要再讨论了。
因此,您需要做的是决定是要坚持使用子类型,还是想要使用Either。对于您的用例,子类型完全正常,因此只需删除Either并实现type FoodType
,例如, Grass
它会起作用,正如你在同一行的评论中注意到的那样。
BTW你的Food
是一个班级,但请注意我说的“特质或抽象类”。这是最佳实践原则;如果你不期望通过Food
需要一个new Food
的实例(你不是;你只是要实例化它的子类,例如new Grass
),那么最好不要首先允许这样的实例化。
另一个提示是制作这样的特征/抽象类sealed
和子类型final case class
,这意味着没有其他人可以提供额外的选项(即,引入一些自己的定制食物):
sealed trait Food
final case class Grass extends Food
final case class Rice extends Food
final case class Fish extends Food
Case类(与标准类相对)服务器的目的是为您定义一些开箱即用的东西,例如
Grass()
代替new Grass()
但是我很分歧:)希望这会有所帮助。
修改强>
好的,现在我意识到你的实际问题。您需要引入另一种总和类型。你已经有Food
,但现在你需要“牛食”。您可以轻松地对其进行建模,添加CowFood
特征,其扩展为Food
并由Grass
和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
(记住,特征是可堆叠的;草是牛食物和马食物)
我不是分类型的忠实粉丝,但对于这个特殊的问题,这是一个更清晰的解决方案,而不是纠缠于Eithers并映射到整个地方。
答案 1 :(得分:0)
在上面的代码中,FoodType定义了两次,它在Cow
中的定义会影响Animal
中的定义 - 这是两种不同的类型。在这种情况下,您不需要Either
。您可以使用eat
类型的参数定义Food
方法,只需传递Grass
,Rice
或Fish
,因为所有这些类都继承自{{1} }
该示例无法编译,因为它需要Food
类型的参数,但传递类型为Either[Grass, Rice]
的参数。