在Scala中使用类型类的缺点

3

有一些框架完全采用类型类模式,scalaz和shapeless是很好的例子。因此,在某些情况下,类型类比普通的Java类和多态性更可取。

我非常钦佩隐式证据表达的能力,并且想知道为什么这种方法在实际应用中缺乏。什么原因促使Scala程序员使用基本类?类型类显然会增加代码量和运行时间,但是否还有其他原因呢?

我学习Scala时没有Java经验,不知道经典的Scala-Java类可能带来哪些重要的好处。

我正在寻找一些引人注目的使用案例,展示类型类无法胜任或效果不佳的领域。


我不确定我是否同意这个问题的前提(至少是“缺乏实际应用”的部分)。类型类在Scala标准库中扮演着核心角色(甚至在Java中也可以通过正确地看待例如Comparator来展示出来)。 - Travis Brown
Scalaz和Shapeless使用Monad抽象重新实现了许多功能。Scala标准库的选择肯定有一些原因,但它们中的经典类并不是杀手级功能,因为类型类可以满足相同的用例。 - ayvango
我撤回我的答案。这个问题会产生不同的意见。 - wheaties
冗长的解释毕竟是一种好的解释。我只是有一个想法,可能有更强的理由。 - ayvango
1个回答

4

类型类和继承都能以不同的方式实现代码重用。继承适用于提供针对内部变化的正确功能。

class Foo { def foo: String = "foo" }
def fooUser(foo: Foo) { println(foo.foo) }

class Bar extends Foo {
  private var annotation = List.empty[String]
  def annotate(s: String) { annotation = s :: annotation }
  override def foo = ("bar" :: annotation.map("@" + _)).mkString(" ")
}

现在,只要您给他们一个Bar,即使他们只知道类型为Foo,每个使用Foo的人都能获得正确的值。 您不必预测您可能需要可插入功能(除了不将foo标记为final)。 您无需跟踪类型或将见证实例向前传递;您只需在任何需要Foo的地方使用Bar并且它会做正确的事情。 这是一件大事。 如果您想要一个固定的接口,并且底层具有易于修改的功能,则继承是您的选择。
相比之下,当您拥有一组固定的数据类型具有易于修改的接口时,继承并不那么好。 排序是一个很好的例子。 假设你想对Foo进行排序。 如果您尝试
class Foo extends Sortable[Foo] {
  def lt(you: Foo) = foo < you.foo
  def foo = "foo"
}

你可以将这个传递给任何能够对Sortable进行排序的内容。但是,如果你想按名称长度而不是标准排序进行排序怎么办?那么,

class Foo extends LexicallySortable[Foo] with LengthSortable[Foo] {
  def lexicalLt(you: Foo) = foo < you.foo
  def lengthLt(you: Foo) = foo.length < you.foo.length
  def foo = "foo"
}

这很快就变成了一场无望的任务,特别是当你不得不追踪所有Foo的子类并确保它们被正确更新时。相比之下,最好将小于比较委托给一个类型类,这样您就可以根据需要轻松更换它。(或者使用常规类,但必须明确引用)。这种自动选择的功能也非常重要。
你不能真正用一个替换另一个。当你需要将新的数据类型轻松地纳入固定接口时,请使用继承。当您需要几种基础数据类型但需要轻松提供新功能时,请使用类型类。当两者都需要时,无论您采取哪种方法,都需要大量的工作,所以请根据需要使用。

这让我想起了“表达式问题”(约在25:00处) - Peter Schmitz

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