2017-02-27 22 views
1

考虑斗牛犬:如何指示Scala不要渲染抽象类型?

trait Animal { 
    type Food 

    def defaultFood(): Food 
} 
class Bulldog extends Animal { 
    type Food = Steak 

    ... implementations ... 
} 

Bulldog.defaultFood()工作得很好的编译器(虽然我的语法高亮显示了一个错误,这也没什么大不了的)功能:

val bulldog = new Bulldog() 
val df: bulldog.Food = bulldog.defaultFood() 

但是,如果牛头犬封装内的另一个class,all hell break loose:

class Kennel(val animal: Animal) { 
} 
def getSupply(kennel: Kennel): kennel.animal.Food = { 
    ... implementation ... 
} 

val kennel = new Kennel(bulldog) 
val df2: bulldog.Food = getSupply(kennel) 

Scala编译器会抛出编译错误:

type mismatch; 
found : Option[kennel.animal.V] where val kennel: Kennel 
required: bulldog.Food 

此功能目前在斯卡拉缺失吗?有什么办法可以使它工作吗?

+0

一个问题:你是不是打算在牛头犬实现中编写'type Food = Steak',而不是'object Food'? –

+0

对不起,我修好了。 – tribbloid

+0

你还没有定义什么'getSupply()'_does_,只是它有一个返回类型。 – jwvh

回答

3

您的代码有一个汇编问题 - kennel.animal是无法解析的,因为Kennel类不会公开animal作为公共字段;这可以通过在animal之前添加val来轻松解决。

什么似乎打扰你,虽然是Kennel不知道潜在的动物,除了它是一个Animal。传递斗牛犬被认为是传递一些动物。

尝试加入一些更多的类型信息,以Kennel

class Kennel[A <: Animal](val animal: A) { } 

def getSupply[A <: Animal](kennel: Kennel[A]): kennel.animal.Food 

val kennel = new Kennel(bulldog) 
val df2: bulldog.Food = getSupply(kennel) 

现在Kennel知道的animal(例如Bulldog)和getSupply能够返回食物的确切类型对于动物的确切类型。

下面是完整的工作代码,您可以尝试:

trait Steak { 
    override def toString() = "steak" 
} 

trait Animal { 
    type Food 
    def defaultFood(): Food 
} 

class Bulldog extends Animal { 
    type Food = Steak 
    def defaultFood() = new Steak {} 
} 

class Kennel[A <: Animal](val animal: A) 

object Test extends App { 

    def getSupply[A <: Animal](kennel: Kennel[A]): kennel.animal.Food = kennel.animal.defaultFood() 

    val bulldog = new Bulldog() 
    val kennel = new Kennel(bulldog) 
    val df2: bulldog.Food = getSupply(kennel) 

    println(df2) // prints "steak" 
} 
+0

我有一个轻微的分拆问题。为什么要这样做呢?为什么动物trait只声明'defaultFood()'方法是不够的,该方法返回另一个特征“Food”的实例?为什么Kennel和getSupply()是通用的,当传递的参数肯定会是特性的一个实现(当然,如果'Animal'被设置为参数类型)? –

+1

@AleksandarStojadinovic这取决于你需要什么。我认为@tribbloid希望拥有'牛排'类型的'df2',它不仅是任何食物,还是牛头犬最喜欢的食物。同样,如果你说这个参数的类型是'Animal',你就知道传入的参数是它的一个实现,但你不知道是哪一个。你失去了不同动物之间的区别。通过使用'A <:Animal',您可以参考使用的确切'A'。这样,狗舍不仅拥有“某种动物类型”,而且知道哪种动物类型(在我们的例子中,“斗牛犬”)。 – slouc

+0

好的,tnx。我想这也是来自你来自的学校(或你在你身上所拥有的Java/C#影响的数量:-)) –

0

参数多态(即,类型类)是一个更好的方式来建模 这一点。这里的主要操作是你有一个动物,你必须 检索它最喜欢的食物的一个实例。把它变成一个类型特定的 ,并为特定的动物和他们最喜欢的食物提供实例。 例如: -

@annotation.implicitNotFound(
    "Couldn't confirm that ${Animal}'s favourite food is ${Food}") 
trait DefaultFood[Animal, Food] { def apply(animal: Animal): Food } 
object DefaultFood { 
    /** Helper to easily implement typeclass instances. */ 
    class Impl[Animal, Food](getFood: Animal => Food) 
    extends DefaultFood[Animal, Food] { 
    override def apply(animal: Animal): Food = getFood(animal) 
    } 
} 

class Bulldog 
object Bulldog { 
    type Steak = String // Or whatever. 

    implicit val defaultFood: DefaultFood[Bulldog, Steak] = 
    new DefaultFood.Impl(_ => "Steak") 
} 

class Kennel[Animal, Food](
    animal: Animal)(implicit defaultFood: DefaultFood[Animal, Food]) { 
    def getSupply: Food = defaultFood(animal) 
} 

object Test { 
    val kennel = new Kennel(new Bulldog) // Kennel[Bulldog, Bulldog.Steak] 
} 

这向前传播的一般类型分为Kennel类,但由于 Scala的类型推断的,你实际上并没有拼出来 的类型。 @implicitNotFound注释也给你一个很好的 编译错误消息,如果没有特定的动物及其食物的类型实例。

注意,AnimalFood类型不实际上需要被 动物和食物;这只是我们在这里使用的语义。如果你用最普通的方式看它,这个类型实际上是 只是A => B类型的函数,对于某些AB