最近、Scala ブロガーの間で型クラスパターンに熱狂しているようです。このパターンでは、単純なクラスに、何らかの特性またはパターンに準拠する追加のクラスによって機能が追加されます。非常に単純化された例として、単純なクラス:
case class Wotsit (value: Int)
Foo トレイトに適合させることができます:
trait Foo[T] {
def write (t: T): Unit
}
この型クラスの助けを借りて:
implicit object WotsitIsFoo extends Foo[Wotsit] {
def write (wotsit: Wotsit) = println(wotsit.value)
}
型クラスは通常、コンパイル時に暗黙的にキャプチャされ、Wotsit とその型クラスの両方を一緒に高階関数に渡すことができます。
def writeAll[T] (items: List[T])(implicit tc: Foo[T]) =
items.foreach(w => tc.write(w))
writeAll(wotsits)
(あなたが私を訂正する前に、私はそれが過度に単純化された例だと言いました)
ただし、暗黙の使用は、アイテムの正確なタイプがコンパイル時にわかっていることを前提としています。私のコードでは、多くの場合そうではないことがわかります。あるタイプのアイテム List[T] のリストがあり、それらを操作するための正しいタイプ クラスを検出する必要があります。
Scala で提案されているアプローチは、呼び出し階層のすべてのポイントに typeclass 引数を追加することです。これは、コードがスケーリングされ、これらの依存関係がますます無関係になるメソッドを介して、ますます長いチェーンに渡される必要があるため、煩わしいものになる可能性があります。これにより、Scala の目的とは逆に、コードが雑然として保守が難しくなります。
通常、これは依存性注入が介入する場所であり、ライブラリを使用して、必要な時点で目的のオブジェクトを提供します。詳細は、DI 用に選択されたライブラリによって異なります。私は過去に Java で独自のライブラリを作成しました。
問題は、型クラスの場合、コンパイル時に正確な値がわからないことです。ポリモーフィック記述に基づいて選択する必要があります。そして重大なことに、型情報はコンパイラによって消去されています。マニフェストは型消去に対する Scala のソリューションですが、マニフェストを使用してこの問題に対処する方法は、私にはまったくわかりません。
これに取り組む方法として、人々はどのような手法と Scala の依存性注入ライブラリを提案しますか? 私はトリックを逃していますか?完璧な DI ライブラリ? それとも、これが本当に問題になっているように見えるのでしょうか?
明確化
これには本当に2つの側面があると思います。最初のケースでは、型クラスが必要なポイントは、そのオペランドの正確な型がわかっているポイントからの直接の関数呼び出しによって到達されるため、十分なタイプ ラングリングとシンタックス シュガーにより、型クラスを必要な点です。
2 番目のケースでは、2 つの点がバリアによって分離されています。たとえば、変更できない API、データベースやオブジェクト ストアに保存されている API、シリアル化されて別のコンピューターに送信されている API などです。つまり、型クラスは変更できないことを意味します。オペランドとともに渡されません。この場合、型と値が実行時にしか分からないオブジェクトがある場合、何らかの方法で型クラスを検出する必要があります。
関数型プログラマーは最初のケースを想定する習慣があると思います-十分に高度な言語では、オペランドの型は常に認識可能です。David と mkniessl はこれについて良い答えを提供してくれました。私はそれらを批判したくありません。しかし、2 番目のケースは確かに存在するため、依存性注入を問題に持ち込んだのはそのためです。