Scala类型类模式与模式匹配或重载

时间:2018-05-16 14:05:53

标签: scala design-patterns functional-programming pattern-matching typeclass

我很难为提出一个好的心理模型,当一个问题非常适合Type Classes模式时?

最近我正在使用像

这样的模型
sealed trait FooBar
case class Foo() extends FooBar
case class Bar() extends FooBar

直观地说,我只是模式匹配

def handle(x: FooBar) = x match {
  case f: Foo => println("foo")
  case b: Bar => println("bar")
}

或明确使用子类型/重载

object Overloading {
  def handle(x: Foo) = println("foo")
  def handle(x: Bar) = println("bar")
}

另一方面,类型类方法很冗长,我认为使用它没有任何好处:

trait FooBarThing[T <: FooBar] {
  def handle(x: T): Unit
}

object TypeClass {

  implicit object HandleFoo extends FooBarThing[Foo] {
    def handle(x: Foo) = println("foo")
  }

  implicit object HandleBar extends FooBarThing[Bar] {
    def handle(x: Bar) = println("bar")
  }

  def process[T <: FooBar](x: T)(implicit ev: FooBarThing[T]): Unit = {
    ev.handle(x)
  }
}

我发现很多文章解释了如何来编写类型类,但在 时没有太多?

2 个答案:

答案 0 :(得分:2)

Typeclass模式提供了实现 ad-hoc多态的可能性。 也就是说,如果你有一些多态函数foobar,它必须使用许多不同的类型T,然后你有一些具体的类型T1,它没有实现任何提供{{{1}的接口。 1}},您可以按照以下方式将foobar附加到foobar

T1

在上面的例子中,您可以看到两件事:

  1. trait FoobarTypeclass[T] { def foobar(t: T): Unit } def functionThatRequiresFoobar[T: FoobarTypeclass](t: T): Unit = { for (i <- 1 to 10) implicitly[FoobarTypeclass[T]].foobar(t) } // note that `functionThatRequiresFoobar` knows nothing about `T1` at this point class T1 implicit object AdHocFoobarForT1 extends FoobarTypeclass[T1] { def foobar(t: T1): Unit = println("foobar now works on T1, awesome!") } functionThatRequiresFoobar(new T1) // but here, it works anyway! FoobarTypeclass都不知道具体类型functionThatRequiresFoobar
  2. 的存在
  3. T1类型还必须对T1FoobarTypeclass一无所知。
  4. 这意味着,functionThatRequiresFoobarT1 完全解耦。但是在示例的最后一行,

    functionThatRequiresFoobar
    无论如何,

    工作得很好,因为functionThatRequiresFoobar(new T1) 类型类以特别的方式将AdHocFoobarForT1的实现附加到类foobar

    类似地,您可以使用此模式以特定方式实现接口&#34;在未在其继承层次结构中声明任何相关接口的类上。这反过来允许您将完全独立的库粘合在一起,只需在此处提供一些类型类。

答案 1 :(得分:0)

Andrey Tyukin已经回答了你何时可以使用类型类,所以我只想添加为什么在FooBar是密封类型或者你不需要 ad-hoc多态时更喜欢模式匹配而不是重载

通常,重载会给类型系统带来很多麻烦,并且使得implicits的使用更加困难。 Question on SO讨论了重载的其他缺点,但其中包括:

  • 难以将方法提升为功能
  • 将隐式视图应用于重载函数的参数时出现歧义。

我只会在为未连接类型提供相同功能的情况下使用重载,以创建更好的程序员体验,例如。

object Printer {
    def print(a: Bool): String = ???
    def print(a: Int): String = ???
}

由于您可以对子类型进行模式匹配,因此我肯定会使用该选项。