我正在阅读并逐步使用类型类,并且从Shapeless指南中发现了这种定义类型类的方法:

因此,这里有一个例子:

object CsvEncoder {
  // "Summoner" method
  def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] =
    enc
  // "Constructor" method
  def instance[A](func: A => List[String]): CsvEncoder[A] =
    new CsvEncoder[A] {
      def encode(value: A): List[String] =
        func(value)
      }
    // Globally visible type class instances
}


我不明白需要apply方法吗?在上述情况下,这是做什么的?

稍后,该指南介绍了如何创建类型类实例:

implicit val booleanEncoder: CsvEncoder[Boolean] =
  new CsvEncoder[Boolean] {
    def encode(b: Boolean): List[String] =
      if(b) List("yes") else List("no")
  }


实际上缩短为:

implicit val booleanEncoder: CsvEncoder[Boolean] =
instance(b => if(b) List("yes") else List("no"))


所以我现在的问题是,这如何工作?我没有得到需要apply方法的信息?

编辑:我遇到了一篇博客文章,描述了创建类型类的步骤,如下所示:


定义类型类合同特征Foo。
使用一个辅助方法应用程序定义一个伴随对象Foo,该方法的作用类似于隐式,并且是一种通常从函数定义Foo实例的方法。
定义FooOps类,用于定义一元或二进制运算符。
定义从Foo实例隐式提供FooOps的FooSyntax特征。


那么第2、3和4点的处理方式是什么?

最佳答案

这些实践大多数来自Haskell(基本上是模仿Haskell的类型类的意图是产生大量样板的原因),其中一些只是为了方便。所以,

2)正如@Alexey Romanov所述,与apply一起使用的对象只是为了方便起见,因此可以代替implicitly[CsvEncoder[IceCream]]编写CsvEncoder[IceCream](aka CsvEncoder.apply[IceCream]()),这将返回所需的类型类实例。

3)FooOps为DSL提供便利的方法。例如,您可能会遇到以下情况:

trait Semigroup[A] {
   ...
   def append(a: A, b: A)
}

import implicits._ //you should import actual instances for `Semigroup[Int]` etc.
implicitly[Semigroup[Int]].append(2,2)


但是有时调用append(2,2)方法很不方便,因此提供一个符号别名是一个好习惯:

  trait Ops[A] {
    def typeClassInstance: Semigroup[A]
    def self: A
    def |+|(y: A): A = typeClassInstance.append(self, y)
  }

  trait ToSemigroupOps {
    implicit def toSemigroupOps[A](target: A)(implicit tc: Semigroup[A]): Ops[A] = new Ops[A] {
      val self = target
      val typeClassInstance = tc
    }
  }

  object SemiSyntax extends ToSemigroupOps


4)您可以按以下方式使用它:

import SemiSyntax._
import implicits._ //you should also import actual instances for `Semigroup[Int]` etc.

2 |+| 2


如果您想知道为什么这么多样板,以及为什么scala的implicit class语法没有从头开始提供此功能-答案是implicit class实际上提供了一种创建DSL的方法-功能不那么强大-((主观上))很难提供操作别名,处理更复杂的调度(必要时)等。

但是,有一个宏解决方案可以为您自动生成样板:https://github.com/mpilquist/simulacrum



关于CsvEncoder示例的另一个重要点是,instance是创建类型类实例的便捷方法,而apply是“召集”(要求)这些实例的快捷方式。因此,第一个用于库扩展程序(一种实现接口的方法),另一个用于用户(一种调用为该接口提供的特定操作的方法)。

08-27 22:53