Scala中的哪些类型类有用?

Mic*_*ael 66 scala implicit

据我所知,在这篇博文 中,Scala中的"类型类"只是一个用特征和隐式适配器实现的"模式".

正如博客所说,如果我有特征A和适配器,B -> A那么我可以调用一个函数,它需要类型A的参数,参数类型B而不显式调用此适配器.

我发现它很好但不是特别有用.您能给出一个用例/示例,它显示了此功能的用途吗?

Kev*_*ght 85

一个用例,根据要求......

想象一下,你有一个列表,可以是整数,浮点数,矩阵,字符串,波形等.鉴于此列表,您想要添加内容.

这样做的一个方法是有一些Addable特征必须由可加在一起每一个类型,或隐式转换被继承到Addable,如果处理对象从第三方库,你不能加装接口.

当您还想开始添加可以对对象列表执行的其他此类操作时,这种方法会变得很快.如果你需要替代方案,它也不能很好地工作(例如;添加两个波形会连接它们,还是覆盖它们?)解决方案是ad-hoc多态,您可以在其中挑选和选择要改装到现有类型的行为.

对于原始问题,您可以实现一个Addable类型类:

trait Addable[T] {
  def zero: T
  def append(a: T, b: T): T
}
//yup, it's our friend the monoid, with a different name!
Run Code Online (Sandbox Code Playgroud)

然后,您可以创建此隐式子类实例,对应于您希望添加的每种类型:

implicit object IntIsAddable extends Addable[Int] {
  def zero = 0
  def append(a: Int, b: Int) = a + b
}

implicit object StringIsAddable extends Addable[String] {
  def zero = ""
  def append(a: String, b: String) = a + b
}

//etc...
Run Code Online (Sandbox Code Playgroud)

然后,对列表求和的方法变得微不足道......

def sum[T](xs: List[T])(implicit addable: Addable[T]) =
  xs.FoldLeft(addable.zero)(addable.append)

//or the same thing, using context bounds:

def sum[T : Addable](xs: List[T]) = {
  val addable = implicitly[Addable[T]]
  xs.FoldLeft(addable.zero)(addable.append)
}
Run Code Online (Sandbox Code Playgroud)

这种方法的优点在于,您可以提供某些类型类的替代定义,或者通过导入控制范围内的隐式,或者通过显式提供其他隐式参数.因此,可以提供添加波形的不同方法,或指定整数加法的模运算.从某些第三方库向类型类添加类型也相当轻松.

顺便说一下,这正是2.8集合API采用的方法.虽然sum方法上定义的TraversableLike,而不是上List,类型类Numeric(也包含的不仅仅是几个业务zeroappend)

  • 更麻烦,更灵活一点.因为它们已命名,所以您可以在Scala中定义类型类的多个变体,并通过将其拉入范围来控制要使用的类型. (18认同)
  • 好极了 它非常接近Haskell类型类,只有这里的语法有点麻烦. (3认同)

Ale*_*nov 32

重读那里的第一条评论:

类型类和接口之间的一个重要区别是,对于类A来说,它必须是接口的"成员",它必须在它自己定义的站点上声明.相比之下,任何类型都可以随时添加到类型类中,前提是您可以提供所需的定义,因此任何给定时间类型类的成员都依赖于当前范围.因此,我们不关心A的创建者是否预期了我们希望它属于的类型类; 如果不是,我们可以简单地创建我们自己的定义,表明它确实属于,然后相应地使用它.因此,这不仅提供了比适配器更好的解决方案,在某种意义上它避免了适配器要解决的整个问题.

我认为这是类型类最重要的优势.

此外,它们正确处理操作没有我们正在调度的类型的参数或具有多个参数的情况.例如,考虑这种类型:

case class Default[T](val default: T)

object Default {
  implicit def IntDefault: Default[Int] = Default(0)

  implicit def OptionDefault[T]: Default[Option[T]] = Default(None)

  ...
}
Run Code Online (Sandbox Code Playgroud)

  • 这确实是一个至关重要的区别:类型`A`不必知道它是类型类的成员,它可以添加到新类型类而不修改`A`本身.与使用普通接口(如在Java中)不同,您必须使"A"实现接口. (7认同)

Itt*_*ayD 9

我认为类型类是向类中添加类型安全元数据的能力.

因此,您首先定义一个类来为问题域建模,然后考虑要添加到其中的元数据.像Equals,Hashable,Viewable等等.这会创建问题域和机制的分离,以使用类并打开子类,因为类更精简.

除此之外,您可以在范围内的任何位置添加类型类,而不仅仅是定义类的位置,还可以更改实现.例如,如果我使用Point#hashCode计算Point类的哈希码,那么我仅限于那个特定的实现,它可能无法为我拥有的特定Point集创建一个良好的值分布.但是如果我使用Hashable [Point],那么我可以提供自己的实现.

[更新示例]作为示例,这是我上周使用的一个用例.在我们的产品中,有几种地图包含容器作为值.例如,Map[Int, List[String]]Map[String, Set[Int]].添加到这些集合可能很冗长:

map += key -> (value :: map.getOrElse(key, List()))
Run Code Online (Sandbox Code Playgroud)

所以我想要一个包装它的函数,这样我就可以写了

map +++= key -> value
Run Code Online (Sandbox Code Playgroud)

主要问题是集合并不都具有添加元素的相同方法.有些人有"+"而有些人有'+'.我还想保留向列表添加元素的效率,所以我不想使用创建新集合的fold/map.

解决方案是使用类型类:

  trait Addable[C, CC] {
    def add(c: C, cc: CC) : CC
    def empty: CC
  }

  object Addable {
    implicit def listAddable[A] = new Addable[A, List[A]] {
      def empty = Nil

      def add(c: A, cc: List[A]) = c :: cc
    }

    implicit def addableAddable[A, Add](implicit cbf: CanBuildFrom[Add, A, Add]) = new Addable[A, Add] {
      def empty = cbf().result

      def add(c: A, cc: Add) = (cbf(cc) += c).result
    }
  }
Run Code Online (Sandbox Code Playgroud)

在这里,我定义了一个类型类Addable,可以将元素C添加到集合CC中.我有2个默认实现:For Lists使用::和其他集合,使用构建器框架.

然后使用这个类型类是:

class RichCollectionMap[A, C, B[_], M[X, Y] <: collection.Map[X, Y]](map: M[A, B[C]])(implicit adder: Addable[C, B[C]]) {
    def updateSeq[That](a: A, c: C)(implicit cbf: CanBuildFrom[M[A, B[C]], (A, B[C]), That]): That  = {
      val pair = (a -> adder.add(c, map.getOrElse(a, adder.empty) ))
      (map + pair).asInstanceOf[That]
    }

    def +++[That](t: (A, C))(implicit cbf: CanBuildFrom[M[A, B[C]], (A, B[C]), That]): That  = updateSeq(t._1, t._2)(cbf)
  }

  implicit def toRichCollectionMap[A, C, B[_], M[X, Y] <: col
Run Code Online (Sandbox Code Playgroud)

特殊位adder.add用于添加元素并adder.empty为新键创建新集合.

为了比较,没有类型类,我将有3个选项:1.为每个集合类型编写一个方法.例如,addElementToSubListaddElementToSet等,这创造了很多样板的实施和污染命名空间2.使用反射来确定该子集是一个列表/设置.这是很棘手的,因为地图是空的开始(当然scala也在这里帮助Manifest)3.通过要求用户提供加法器来使穷人的类型类.所以addToMap(map, key, value, adder),这样的事情很简单


Bra*_*ord 6

我发现这篇博文有用的另一种方式是描述类型类: Monads不是隐喻

在文章中搜索类型类.这应该是第一场比赛.在本文中,作者提供了Monad类型类的示例.


Von*_*onC 5

论坛线程“ 是什么使类型类比特征更好? ”提出了一些有趣的观点:

  • 类型类可以很容易代表的概念是相当困难的代表亚型的存在,如平等排序
    练习:创建一个小的类/特征层次结构,并尝试以一种.equals对每个类/特征进行实现的方式,使得对层次结构中任意实例的操作都可以适当地自反,对称和可传递。
  • 类型类使您可以提供证据,证明“控件”之外的类型符合某些行为。
    别人的类型可以是您的类型类的成员。
  • 您不能用子类型表示“此方法采用/返回与方法接收者相同类型的值”,但是使用类型类可以很简单地实现此约束(非常有用)。这是f边界类型问题(其中F边界类型是通过其自身的子类型进行参数化的)。
  • 在特征上定义的所有操作都需要一个实例 ; 总有this争论。因此,例如,您不能以没有实例的情况下可以调用它的方式来定义fromString(s:String): Foo方法。 在Scala中,这表现为人们拼命尝试对伴侣对象进行抽象。 但这对于类型类很简单,如本monoid示例中的零元素所示trait FooFoo

  • 类型类可以归纳定义;例如,如果您有,则JsonCodec[Woozle]可以JsonCodec[List[Woozle]]免费获得。
    上面的示例说明了“您可以添加在一起的东西”。