子类型与类型类相比的优势

24

如果有的话,面向对象编程(OOP)子类型化相对于类型类有哪些优势?换句话说,既然我们有了类型类,是否仍有理由使用面向对象编程(OOP)子类型化呢?

PS:我是Scala程序员。


1
@Blessed Geek:抱歉,我控制不了它。 @JoshD:我不是在要求逐个比较。我的问题基本上是:既然我们有了类型类,还有使用面向对象编程的理由吗? - Jim
1
我认为,与其使用笼统的“面向对象编程”这个术语,更好的做法是用具体的语言特性来表达。 - Randall Schulz
7
@OscarRyz:我相信这个问题可以客观地回答。此外,我认为这不是一个适合这种问题的网站。请不要滥用关闭问题的权力。和平。 - Jim
7
啊,拜托——别再固执地限制讨论了。我想读到对这个问题的回答。拜托啦!!! - Blessed Geek
6
这个问题是客观的并且可以得到答案。类型类和继承的能力可以进行数学评估。据我所读,Jim并不是在征求意见。然而,Jim,你可以改进一下写作:不要询问“原因”,而是询问是否有任何使用子类型化(subtyping)可以做到而类型类不能的事情。此外,我会用“继承”(inheritance)代替“子类型化”,但这可能会略微更改范围,并且可能并不完全符合你的意思。 - Daniel C. Sobral
显示剩余4条评论
6个回答

14

目前,Scala类型类的语法开销比通过特质继承进行子类型化要大得多,潜在的运行时开销也是如此。想象一种情况,您需要让五十种不同类型的事件符合一个接口以支持事件处理引擎。这时使用特质继承会更加容易。

class MyEvent extends Event{
  val name = "foo"
}

than

class MyEvent{
   val name = "foo"
}

object MyEvent2Event{
   implicit def convert(myEvent:MyEvent) = new Event{ val name = myEvent.name}
}

第二种形式在后期多态性、命名自由度和通用坏酷程度方面具有更大的灵活性,但是打出那五十个转换方法,然后在需要类型类时执行适当的导入将变得非常痛苦。如果您不需要灵活性,很难看到回报。此外,在第二个中存在那个令人讨厌的“new”关键字,它将产生无休止的“这是否过度强调了垃圾收集器”的争论。
对于引入可变状态的混入继承,情况会更糟。考虑以下从生产代码中提取的特质:
trait Locking{
   private val lock = new ReentrantReadWriteLock()

   def withReadLock[T](body: => T):T={
      try{
         lock.readLock.lock()
         body
      }finally{
         lock.readLock.unlock()
      }
   }
   // same for withWriteLock
}

使用混合继承的方法非常方便,而且Scala类型类并不能很好地实现它,因为存在“lock”val。那应该把它放在哪里呢?如果将其放在适配的类中,则会失去大部分特质的封装价值。如果将其放在适配器代码中,则锁不再保护任何内容,因为每次适配时都会锁定不同的锁对象。


关于“是否过度使用垃圾回收器”的争论并不相关。在这种情况下,可以使用“lazy val”代替“def”,对吗? - mokus
我不这么认为。我的所有“def”都有参数,这意味着它们不能是惰性值。我错过了什么吗? - Dave Griffith
哦,嗯...抱歉在喝咖啡之前发帖 ;) - mokus
很好的解释!我想知道第一个问题是否可以通过一些语法糖(例如“typeclass MyEvent as Event {val name =“foo”}”)更容易地编写,然后再由编译器优化开销。 - Manuel Schmidt
只是一个想法 - 通过将隐式转换放入特质中,并将它们组合成一个单一的对象,然后由调用者导入,可以在一定程度上解决具有多个导入的问题。类似这样: “trait MyEvent2Event { implicit def ... };... object MyEventConversions extends MyEvent2Event with MyAnotherEvent2Event ...;”。 - Petr

9
个人而言,我发现在其处理得当的限制范围内,面向对象编程更易处理。换句话说:在你实际上不需要类型类的情况下,我觉得对象更容易理解。
然而,这可能只是典型类型类嵌入对象的语法开销的一个副产品。如果Haskell为某些常见类型类模式提供了一些语法糖,那么这种差异可能会消失。
我发现更有趣的是,Haskell社区表明类型类比对象更强大,因为存在一种微不足道的对象嵌入类型类的嵌入方式,但类型类可以做到对象无法做到的事情。然而,Scala社区表明对象至少与类型类同样强大,因为存在一种微不足道的类型类嵌入对象的嵌入方式。
这似乎表明两者之间的关系比通常想象的更密切。

1请参阅Bruno C.d.S. Oliveira、Adriaan Moors和Martin Odersky的文章Type Classes as Objects and Implicits,以及关于该论文的讨论Lambda the Ultimate,特别是Paul Snively的这篇精彩摘要(强调添加):

Martin Odersky和他的团队在如何在一个统一的面向对象和函数式编程语言中实现类型类方面做出的设计决策仍然产生着有趣的成果。在我看来,随着对这篇论文的快速阅读,隐式转换看起来越来越不像“穷人版的类型类”,而更像是类型类的一种改进。


3
类型类无法进行实现继承。 - Daniel C. Sobral

7
在非纯函数语言中,子类型让您可以在相同的用法下具有不同的副作用;这通常很难通过类型类来实现。(当然,您可以实现它,只是对我来说似乎比较笨拙。)
此外,子类型可能更加高效——它是一种缓存“X是Y”的信息而不需要重复转换(或编译器英雄主义来缓存该信息)的方法。对于非常深层次的层次结构,这可能是一个问题。

3

1
我已经看过那个链接了。我不是在要求逐个比较。我的问题基本上是:既然我们有了类型类,还有什么理由继续使用面向对象编程? - Jim
抱歉,您原来的问题对我来说不太清楚;这个更好。 - Lee Reeves
我认为类型类已经存在一段时间了。可能是你刚刚发现它们。所以对于你自己的问题,答案完全取决于你:Jim,既然你已经发现了类型类,那么你是否仍然需要面向对象编程? - OscarRyz

3
Scala至少有一个不同之处是子类型链可以直接使用,而类型类链则更加棘手。如果我们有类型A、B和C,那么如果A < B且B < C,则必然有A < C。但是,如果A <% B和B <% C,则不一定有A <% C。这是因为Scala编译器不会应用多个隐式转换,否则类型推断会变得困难,并且(如我所记得的)可能是不可决的。

2
继续支持OOP的实际原因是互操作性。 BitC讨论中目前存在的一个问题是是否将单一继承添加到语言中。在其对正式类型系统和类型推断的影响方面,存在实用的利弊和问题。一段时间以来,类型类的实例解析机制让我们相信,由于缺乏链接安全性,类型类基本上存在缺陷。在缺乏词法范围解析机制的情况下,类型类实例解析无法按人类术语进行扩展:一个开发组的更改可能会导致完全不同的组编写的应用程序中出现链接错误。这使我们不情愿地研究了单一继承和某种形式的F<+SelfType方案。当实例具有多个具有不同特化程度的解析时,也存在相关问题。我们后来采取了一种解决这个问题的方法。我们正在努力解决的问题是(a)是否需要子类型化BitC程序,如果需要,为什么需要;(b)即使我们不需要,与面向对象语言中的程序的互操作性是否仍然要求我们支持一个可以表达继承的类型系统,因此需要使用可以使用继承的语言。这些都不是对OP问题的任何决定性答案。我想说的是,在这里的问题超出了任何特定语言的设计。还需要考虑人类因素和互操作性问题。Jonathan Shapiro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接