将多个数据类型传递给Type变量

时间:2018-03-27 08:53:34

标签: scala

我正在尝试传递GrassRice对象。但是,它编译失败了。我根据此链接using Either尝试了以下Either[]选项。但是,它不起作用。

我想像这样限制传递Fish Object。我只想传递RiceGrass

(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")
    }
}

我的问题:填写上述代码的更好方法是什么,我只能传递RiceGrass对象。(如果没有,则如何实现其他方式)并限制Fish对象。

2 个答案:

答案 0 :(得分:3)

这样可行:

(new Cow).eat(Left[Grass, Rice](new Grass))

但是您还有另一个问题 - 您定义了抽象类Animal以期望类型FoodTypeFood的子类型。类型GrassRice都是Food的单独有效子类型,但Either[Grass, Rice]不是。

一些潜在的理论:

当你使用一种从几种可能的形式中选择一种的类型时,它被称为和类型。与产品类型相反,产品类型将所有给定类型组合成一个实体(例如,Person由字符串名字,字符串姓氏,整数年龄等组成), sum类型< / em>只从所有可能的实现中获取一个实现。这就是你所拥有的 - 你的FoodType是草,米或鱼。

这里的问题是,您正在使用两种不同的结构来接近您的总和类型,这两种结构都用于建模和类型的相同目的。一种方法是使用特征或抽象类,然后通过所有可能的选项进行扩展:

trait Food
class Grass extends Food
class Rice extends Food
class Fish extends Food

另一种方法是使用开箱即用的总和类型,例如EitherEither笨拙的事实是,它只需要两种可能性,所以对于三种你可能必须有这样的可能性。 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类(与标准类相对)服务器的目的是为您定义一些开箱即用的东西,例如

  • equals(),copy()等方法
  • 支持模式匹配(通过实施apply / unapply for you)
  • 默认随播广告对象,可让您使用Grass()代替new Grass()

但是我很分歧:)希望这会有所帮助。

修改

好的,现在我意识到你的实际问题。您需要引入另一种总和类型。你已经有Food,但现在你需要“牛食”。您可以轻松地对其进行建模,添加CowFood特征,其扩展为Food并由GrassRice扩展。

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方法,只需传递GrassRiceFish,因为所有这些类都继承自{{1} }

该示例无法编译,因为它需要Food类型的参数,但传递类型为Either[Grass, Rice]的参数。