关于《Scala 编程(3rd)》中, 20.6 抽象类型 P424 的疑问

2019-07-03 23:59:28 +08:00
 w4ngzhen

在《 Scala 编程( 3rd )》中,20.6 抽象类型这一节中,本人有个非常不解的地方,书中提到:

class Food

abstract class Animal {
  def eat(food: Food)
}

class Grass extends Food

class Cow extends Animal {
  override def eat(food: Grass) = {}	// 这不能编译
}					// 如果能的话...

class Fish extends Food

val bessy: Animal = new Cow

bessy eat (new Fish) // ...你就能给牛吃草了

本人理解 override 超类方法,方法参数是不支持协变的,但是上文假设通过编译,"bessy eat (new Fish)"不是本来就不能运行吗? Fish 本来就不能上转型为 Grass 呀。何况如下重写 eat 方法岂不是同样有这个问题?

class Cow extends Animal {
  override def eat(food: Food): Unit = {}
}

val cow = new Cow

cow eat new Fish
// 能通过编译且运行

小弟不才,一直没理解,希望各位大佬轻喷

5072 次点击
所在节点    Scala
18 条回复
ech0x
2019-07-04 07:02:17 +08:00
class Fish extends Food
你已经指定 Fish 是可以被牛吃的一种食物了啊。
ech0x
2019-07-04 07:04:20 +08:00
名字只是一个代称,写什么都是没有问题的,类与类之间的关系才是重要的。
w4ngzhen
2019-07-04 07:22:29 +08:00
@ech0x 这位老师您看哈,上述假设的地方假如成立,那么 Cow 确实有一个 eat(Grass)的方法,这个方法依然不能接受 Fish 实例呀
ech0x
2019-07-04 07:27:35 +08:00
@w4ngzhen 别叫我老师😂我其实不会 Scala。
仍然不接受 Fish 所以呢?
ech0x
2019-07-04 07:28:28 +08:00
@w4ngzhen 我没明白你想表达什么。
w4ngzhen
2019-07-04 08:03:53 +08:00
@ech0x 所以这样不是更能够限定接受的参数类型吗,但是书中表示如果参数能够协变,反而会出现 cow eat new Fish 的情况
ech0x
2019-07-04 08:38:04 +08:00
class Cow extends Animal {
override def eat(food: Fish) = {}
}
这样不就出现了
cow eat new Fish 的情况?
ech0x
2019-07-04 08:39:18 +08:00
哦,我弄错了,问题不在这里。
ech0x
2019-07-04 08:42:00 +08:00
我知道问题在哪里了,你注意看 bessy 的类型,bessy 的类型不是 Cow 是 Animal
w4ngzhen
2019-07-04 09:39:03 +08:00
@ech0x 额,是否是这样的, [错误假设] 假如成立的时候,能通过编译,但在运行时调用为 eat ( Grass )方法,所以会有转型出错这样的不安全情况?但是这样又说不通 override def eat(food: Food),cow 依然能够 eat Fish 的情况啊。
madeye
2019-07-04 09:48:28 +08:00
简单来说,你不能用

···
def eat(food: Grass)
···

去 override

···
def eat(food: Food)
···

这两个 function 的 signature 并不一样,编译时就会报错。

···
method eat overrides nothing.
Note: the super classes of class Cow contain the following, non final members named eat:
def eat(food: Playground.this.Food): Unit
···

https://scastie.scala-lang.org/Gq2lJuhRTnCM8Q46BSmA6g
dcalsky
2019-07-04 09:53:28 +08:00
@w4ngzhen 为什么不编译一下呢
w4ngzhen
2019-07-04 10:07:50 +08:00
@dcalsky
@madeye
两位老师,这个我自己已经写过了并且也能理解。现在核心就是不能理解书上说的假设能够通过编译后的解释,就拿我第二段代码来说,能够通过编译且能正常运行,这个时候就违背了语义了呀( cow eat Fish ),现在书上意义就是说,我们 [不能] 通过参数协变来 override 超类的方法,因为如果那样,就会出现 cow eat Fish 的情况;但是在我那段代码中(第二段):我也没有使用错误的 override,但是依然出现了 cow eat Fish 的情况。
ech0x
2019-07-04 10:32:20 +08:00
@madeye 惊现 madeye 大佬。
dcalsky
2019-07-04 10:38:32 +08:00
@w4ngzhen 没问题啊 只不过是你命名语义的问题。第二段代码,你声明了 Cow 可以 Eat Food,而 Fish 是 Food 的子类,所以 fish 可以作为参数传入。我举个例子好了,有一个 array[HTMLDOM],,你把 P,Button,Input 这些 HTMLDOM 的子类 add 进入都没问题。你的 Food 就是对应了 HTMLDOM,而 Fish 对应了这里的 Button,所以是没问题的。
madeye
2019-07-04 12:10:30 +08:00
"假设能够通过" 在英文里是虚拟语态,本意就是这件事是不可能,否则会造成不合理的结果。

···
class Food
abstract class Animal {
def eat(food: Food)
}
class Grass extends Food
class Cow extends Animal {
override def eat(food: Grass) = {} // This won't compile,
} // but if it did,...
class Fish extends Food
val bessy: Animal = new Cow
bessy eat (new Fish) // ...you could feed fish to cows.
···

英文原文是 “ but if it did,...”,翻译成 "但如果这都能编译过的话" 可能会更好一些。
w4ngzhen
2019-07-04 13:00:46 +08:00
@madeye 但是我还是很纠结这个的描述啊,您说“本意就是这件事是不可能,否则会造成不合理的结果。”但是我使用正确的方式 override,依然是不合理的结果呀(见我的第二段代码)。
madeye
2019-07-04 17:32:20 +08:00
@w4ngzhen 因为你改变了原来代码的语意,cow 吃的 grass,而不是泛指的 food。严格来说来说,第二段代码算是个 bug,因为没有定义清楚 eat 这个函数所接受的类型。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/579808

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX