2017-04-24 7 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] = 
instance(b => if(b) List("yes") else List("no")) 

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

が実際に短縮されましたか?私が得られないことは、適用メソッドの必要性ですか?

EDIT:

  1. は型クラスの契約トレイトはFooを定義します。私は以下のように型クラスを作成する手順を説明したブログ記事に出くわしました。
  2. 暗黙のように機能するヘルパーメソッドapplyと、通常は関数からFooインスタンスを定義する方法で、コンパニオンオブジェクトFooを定義します。
  3. 単項演算子または二項演算子を定義するFooOpsクラスを定義します。
  4. FooインスタンスからFooOpsを暗黙的に提供するFooSyntax特性を定義します。

ポイント番号2,3,4の取引とは何ですか? object CsvEncoder定義した直後のガイドを引用

答えて

3

習慣はHaskellから来ました(基本的にHaskellの型クラスを模倣する意図はあまりにも多くの定型文の理由です)。その一部は便宜上のものです。だから、@Alexeyロマノフが述べたように

2)は、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つはユーザー用です(そのインターフェース用に特定の操作を呼び出す方法)。

1

apply方法...私たちは、ターゲット型指定された型クラスのインスタンスを召喚することができます:それらのほとんどは

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

タイプクラスを作成するこのプラクティスについて詳しく教えてください。私は今質問を編集しました! – sparkr

1

もうひとつ注意しなければならないのは、シェイプレスでは、applyメソッドは、より洗練された構文だけではありません。

たとえば、この簡略化されたシェイプレスのバージョン「Generic」と、いくつかのケースクラス「Foo」を取り上げます。私は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] { type Repr = Int :: String :: HNil }として型指定されたインスタンスを取得します。しかし、私がimplicitly[Generic[Foo]]と呼んだ場合、すべてのコンパイラはその結果を知っています。それはGeneric[Foo]です。言い換えれば、具体的なタイプのReprが失われてしまい、私はそれに役立つものは何もできません。

def implicitly[T](implicit e: T): T = e 

メソッドの宣言は、基本的に言うこと:あなたがTを求めるならば、私は1、そして何よりもを見つけた場合、私は、あなたにTを与えることを約束した理由は、次のようにimplicitlyが実装されていることです。つまり、implicitly[Generic[Foo] { type Repr = Int :: String :: HNil }]と尋ねなければならないということです。それは自動派生の目的を破っています。

+0

うわー!私はあなたにその甘い説明のための投票を与えます! – sparkr

関連する問題