2017-04-24 110 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