2017-04-24 106 views
4

我读通过,并通过使用类型类工作我的方式,我对面的无形指南定义类型类的这种方式来:Scala的类型类的最佳实践

所以这里去的例子:

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")) 

所以我现在的问题是,如何工作的呢?我没有得到的是应用方法的需要?

编辑:

  1. 定义类型类合同特质富:我碰到一个描述如下创建类型类的步骤的博客文章来了。
  2. 使用辅助方法定义伴随对象Foo,其作用类似于隐式方式,以及通常从函数定义Foo实例的方式。
  3. 定义定义一元运算符或二元运算符的FooOps类。
  4. 定义从Foo实例隐式提供FooOps的FooSyntax特征。

那么点数2,3和4的交易是什么?

回答

3

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

2)@Alexey罗曼诺夫提到,与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语法不提供从头这个功能 - T的他的回答是,implicit class实际上提供了一种创建DSL的方法 - 它只是不那么强大 - 它(主观上)更难提供操作别名,处理更复杂的调度(当需要时)等。

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


一个关于您CsvEncoder例如另一个重要的一点是instance是用于创建型类的实例的便利方法,但是apply为“召唤”(需要)的那些情况下的快捷方式。所以,第一个是图书馆扩展器(一种实现接口的方法),另一个是为用户(调用为该接口提供的特定操作的方法)。

1

object CsvEncoder定义后,立即引用手册:

apply方法...使我们能够给出目标类型召唤一个类型的类实例:

CsvEncoder[IceCream] 
// res9: CsvEncoder[IceCream] = ... 
+0

请您详细说明创建类型类的这种做法吗?我现在编辑了我的问题! – sparkr

1

另一件需要注意的是,在无形中,apply方法不仅适用于cuter语法。

例如,这个简化版本的无形'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 }],这就失去了自动派生的目的。

+0

哇!我给你一个赞成这个甜蜜解释的投票! – sparkr