我通过使用类型类阅读并努力工作,我遇到了这种从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
}
我不明白需要申请方法吗?在上面这个背景下它做了什么?
稍后,该指南描述了如何创建类型类实例:
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方法吗?
编辑:我发现了一篇博客文章,其中描述了创建类型类的步骤,如下所示:
那么点号2,3和4的处理是什么?
答案 0 :(得分:3)
大多数这些实践来自Haskell(基本上是模仿Haskell类型类的意图是这么多样板的原因),其中一些只是为了方便。所以,
2)正如@Alexey Romanov所提到的,apply
的伴随对象只是为了方便起见,因此您可以只编写implicitly[CsvEncoder[IceCream]]
(又名CsvEncoder[IceCream]
)代替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
是“召唤”(需要)这些实例的快捷方式。因此,第一个用于库扩展器(一种实现接口的方式),另一个用于用户(一种调用为该接口提供的特定操作的方法)。
答案 1 :(得分:3)
需要注意的另一点是,apply
方法在无形状中不仅适用于可编程语法。
以无形'Generic
和一些案例类Foo
的简化版本为例。
trait Generic[T] {
type Repr
}
object Generic {
def apply[T](implicit gen: Generic[T]): Generic[T] { type Repr = gen.Repr } = gen
/* lots of macros to generate implicit instances omitted */
}
case class Foo(a: Int, b: String)
现在,当我致电Generic[Foo]
时,我将获得一个键入Generic[Foo] { type Repr = Int :: String :: HNil }
的实例。但如果我调用implicitly[Generic[Foo]]
所有编译器都知道结果是Generic[Foo]
。换句话说:Repr
的具体类型丢失了,我无法用它做任何有用的事情。原因是implicitly
实现如下:
def implicitly[T](implicit e: T): T = e
该方法声明基本上说:如果你要求T
我保证给你一个T
,如果我找到一个,而不是更多。所以这意味着你必须要求implicitly[Generic[Foo] { type Repr = Int :: String :: HNil }]
并且这无法实现自动推导的目的。
答案 2 :(得分:1)
在object CsvEncoder
定义后立即引用指南:
apply
方法...允许我们在给定目标类型的情况下召唤一个类型类实例:CsvEncoder[IceCream] // res9: CsvEncoder[IceCream] = ...