enrich-my-libraryパターンをScalaコレクションに適用するにはどうすればよいですか?
-
29-10-2019 - |
質問
Scalaで利用できる最も強力なパターンの1つは、enrich-my-library *パターンです。これは、動的なメソッド解決を必要とせずに、既存のクラスにメソッドを追加するために出現への暗黙的な変換を使用します。たとえば、すべての文字列に、空白文字の数をカウントするメソッドspaces
が必要な場合は、次のことができます。
ジェネラコディセタグプレ
残念ながら、このパターンはジェネリックコレクションを処理するときに問題が発生します。たとえば、C
とジェネリック要素タイプA
を使用したenrich-my-libraryパターンの理想的な候補のようです。
ジェネラコディセタグプレ
もちろん、機能しない場合を除きます。 REPLは次のように語っています: ジェネラコディセタグプレ
2つの問題があります。空のC[C[A]]
リストから(または薄い空気から)C[A]
を取得するにはどうすればよいですか?そして、C[C[A]]
の代わりにsame +:
行からSeq[Seq[A]]
を取得するにはどうすればよいですか?
* 以前はpimp-my-libraryと呼ばれていました。
解決
この問題を理解するための鍵は、コレクションライブラリにコレクションを構築して操作する 2つの異なる方法があることを理解することです。 1つは、すべての優れたメソッドを備えたパブリックコレクションインターフェイスです。もう1つは、コレクションライブラリの作成で広く使用されていますが、コレクションライブラリ以外ではほとんど使用されていませんが、ビルダーです。
エンリッチメントの問題は、同じタイプのコレクションを返そうとしたときにコレクションライブラリ自体が直面する問題とまったく同じです。つまり、コレクションを作成したいのですが、一般的に作業する場合、「コレクションと同じタイプ」を参照する方法がありません。したがって、ビルダーが必要です。
問題は、ビルダーをどこから入手するかということです。明らかな場所はコレクション自体からです。 これは機能しません。一般的なコレクションに移行する際に、コレクションのタイプを忘れることをすでに決定しました。そのため、コレクションが必要なタイプのコレクションをさらに生成するビルダーを返すことができたとしても、タイプが何であるかはわかりません。
代わりに、浮かんでいるCanBuildFrom
暗黙的要素からビルダーを取得します。これらは、特に入力タイプと出力タイプを照合し、適切にタイプされたビルダーを提供する目的で存在します。
つまり、2つの概念的な飛躍があります。
- 標準のコレクション操作を使用しておらず、ビルダーを使用しています。
- これらのビルダーは、コレクションから直接取得するのではなく、暗黙の
CanBuildFrom
から取得します。例を見てみましょう。 ジェネラコディセタグプレ
これを分解しましょう。まず、コレクションのコレクションを作成するには、各グループの
C[A]
と、すべてのグループをまとめるC[C[A]]
の2種類のコレクションを作成する必要があることがわかっています。したがって、2つのビルダーが必要です。1つはA
を取得してC[A]
を構築し、もう1つはC[A]
を取得してC[C[A]]
を構築します。CanBuildFrom
のタイプシグネチャを見ると、 ジェネラコディセタグプレこれは、CanBuildFromが、開始しているコレクションのタイプを知りたいことを意味します。この場合は、
C[A]
であり、次に、生成されたコレクションの要素とそのコレクションのタイプです。したがって、これらを暗黙的なパラメータcbfcc
およびcbfc
として入力します。これに気付いたので、それがほとんどの作業です。
CanBuildFrom
を使用して、ビルダーを提供できます(必要なのはそれらを適用することだけです)。そして、あるビルダーは、+=
を使用してコレクションを構築し、それを最終的にresult
を使用することになっているコレクションに変換し、それ自体を空にして、clear
を使用して再開する準備をすることができます。ビルダーは空から開始します。これにより、最初のコンパイルエラーが解決されます。再帰ではなくビルダーを使用しているため、2番目のエラーもなくなります。最後の小さな詳細(実際に作業を行うアルゴリズム以外)は、暗黙的な変換にあります。
new GroupingCollection[A,C]
ではなく[A,C[A]]
を使用していることに注意してください。これは、クラス宣言が1つのパラメーターを持つC
に対するものであり、渡されたA
でそれ自体を埋めるためです。したがって、タイプC
を渡して、それからC[A]
を作成させます。詳細はわずかですが、別の方法を試すとコンパイル時エラーが発生します。ここでは、メソッドを「等しい要素」コレクションよりも少し一般的にしました。むしろ、このメソッドは、順次要素のテストが失敗するたびに元のコレクションを切り離します。
実際のメソッドを見てみましょう: ジェネラコディセタグプレ
動作します!
唯一の問題は、これらのメソッドを配列で使用できないことです。これは、配列で2つの暗黙的な変換が必要になるためです。
WrappedArray
にキャストするなど、いくつかの方法があります。
編集:配列や文字列などを処理するための私の好ましいアプローチは、コードをさらにより一般的にしてから、適切な暗黙の変換を使用して、配列が機能するようにそれらをより具体的にすることです。また。この特定の場合: ジェネラコディセタグプレ
ここに、Iterable[A]
からC
を取得する暗黙的なものを追加しました-ほとんどのコレクションでは、これは単なるIDになります(たとえば、List[A]
はすでにIterable[A]
です)が、配列の場合は、実際の暗黙的な変換になります。その結果、C[A] <: Iterable[A]
の要件を削除しました。基本的には、<%
の要件を明示的にしただけなので、コンパイラに入力させる代わりに、自由に明示的に使用できます。また、コレクションのコレクションがC[C[A]]
であるという制限を緩和しました。代わりに、D[C]
であり、後で必要なものとして入力します。これは後で入力するので、メソッドレベルではなくクラスレベルにプッシュしました。それ以外は基本的に同じです。
問題は、これをどのように使用するかです。通常のコレクションの場合、次のことができます。 ジェネラコディセタグプレ
ここで、C[A]
の場合はC
を、C[C[A]]
の場合はD[C]
をプラグインします。どのタイプが何に対応するかをまっすぐに保つことができるように、new GroupingCollection
の呼び出しで明示的なジェネリック型が必要であることに注意してください。 implicit c2i: C[A] => Iterable[A]
のおかげで、これは自動的に配列を処理します。
しかし、待ってください。文字列を使用したい場合はどうでしょうか。 「文字列の文字列」を作成できないため、問題が発生しています。これは、追加の抽象化が役立つところです。文字列を保持するのに適したものをD
と呼ぶことができます。 Vector
を選び、次のことを行いましょう:
ジェネラコディセタグプレ
文字列のベクトルの構築を処理するために新しいCanBuildFrom
が必要です(ただし、Vector.newBuilder[String]
を呼び出すだけなので、これは非常に簡単です)。次に、GroupingCollection
が適切に入力されるように、すべての型を入力する必要があります。 。すでに[String,Char,String]
CanBuildFromの周りに浮かんでいるので、文字のコレクションから文字列を作成できることに注意してください。
試してみましょう: ジェネラコディセタグプレ
他のヒント
これは、filterMap
操作を尊重する「同じ結果タイプ」をすべてのGenTraversableLike
に追加します。
ジェネラコディセタグプレ
そして、質問の例では、ソリューションは次のようになります。 ジェネラコディセタグプレ
サンプルREPLセッション ジェネラコディセタグプレ
繰り返しになりますが、groupIdentical
がGenTraversableLike
で直接定義されていた場合とまったく同じ方法で、同じ結果タイプの原則が観察されていることに注意してください。
このコミットの時点で、魔法の呪文はマイルズが優れた答えを出したときとは少し異なります。
以下は機能しますが、標準ですか?カノンの1つがそれを修正することを願っています。(むしろ、大砲、大きな銃の1つです。)ビュー境界が上限である場合、配列と文字列への適用が失われます。境界がGenTraversableLikeであるかTraversableLikeであるかは問題ではないようです。ただし、IsTraversableLikeはGenTraversableLikeを提供します。 ジェネラコディセタグプレ
9人の命を持つ猫の皮を剥ぐ方法は複数あります。このバージョンでは、ソースがGenTraversableLikeに変換されたら、GenTraversableから結果を作成できる限り、それを実行するだけです。古い担当者には興味がありません。 ジェネラコディセタグプレ
この最初の試みには、ReprからGenTraversableLikeへの醜い変換が含まれます。 ジェネラコディセタグプレ