21

任意のタイプのコレクションを受け入れ、それを新しいコレクション (同じコレクション タイプで異なる要素タイプ) にマップするメソッドを作成しようとしていますCC[_]が、非常に苦労しています。基本的に私は実装しようとしていますが、コレクション自体にはmap実装していません。

質問

次のようなシグネチャを持つメソッドを実装しようとしています。

def map[CC[_], T, U](cct: CC[T], f: T => U): CC[U]

その使用法は次のとおりです。

map(List(1, 2, 3, 4), (_ : Int).toString) //would return List[String]

どこでもうまくいく答えに興味がCCありArray、私の試み(下記)が最終的にうまくいかなかった理由に興味があります。


私の試み

(せっかちな人のために、以下で説明しますが、これを機能させるにはまったく失敗しています。繰り返しますが、問題は「どうすればそのようなメソッドを作成できるか?」ということです)。

私はこのように始めます:

scala> def map[T, U, CC[_]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
     | cct map f
                                                             ^
 <console>:9: error: value map is not a member of type parameter CC[T]
       cct map f
           ^

OK、それは理にかなっています-それCCは通過可能であると言わなければなりません!

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
     | cct map f
<console>:10: error: type mismatch;
 found   : Traversable[U]
 required: CC[U]
       cct map f
           ^

エラー、OK! たぶん、実際にそのcbfインスタンスを指定した場合。結局のところ、戻り値の型 ( To) をCC[U]次のように指定します。

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
     | cct.map(t => f(t))(cbf)
<console>:10: error: type mismatch;
 found   : scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]]
 required: scala.collection.generic.CanBuildFrom[Traversable[T],U,CC[U]]
       cct.map(t => f(t))(cbf)
                          ^

エラー、OK! それはより具体的なエラーです。使えそうです!

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[Traversable[T], U, CC[U]]): CC[U] = 
     | cct.map(t => f(t))(cbf)
map: [T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: scala.collection.generic.CanBuildFrom[Traversable[T],U,CC[U]])CC[U]

素晴らしい。私は私を持っていますmap!これを使おう!

scala> map(List(1, 2, 3, 4), (_ : Int).toString)
<console>:11: error: Cannot construct a collection of type List[java.lang.String] with elements of type java.lang.String based on a collection of type Traversable[Int].
              map(List(1, 2, 3, 4), (_ : Int).toString)
                 ^

何だって?


観察

当時のトニー・モリスのこの点に関する観察は完全に的を射ていたと思わずにはいられません。彼が何を言ったの?彼は「それが何であれ、それは地図ではない」と言った。これが scalaz スタイルでいかに簡単か見てみましょう:

scala> trait Functor[F[_]] { def fmap[A, B](fa: F[A])(f: A => B): F[B] }
defined trait Functor

scala> def map[F[_]: Functor, A, B](fa: F[A], f: A => B): F[B] = implicitly[Functor[F]].fmap(fa)(f)
map: [F[_], A, B](fa: F[A], f: A => B)(implicit evidence$1: Functor[F])F[B]

それで

scala> map(List(1, 2, 3, 4), (_ : Int).toString)
<console>:12: error: could not find implicit value for evidence parameter of type Functor[List]
              map(List(1, 2, 3, 4), (_ : Int).toString)
                 ^

となることによって

scala> implicit val ListFunctor = new Functor[List] { def fmap[A, B](fa: List[A])(f: A => B) = fa map f }
ListFunctor: java.lang.Object with Functor[List] = $anon$1@4395cbcb

scala> map(List(1, 2, 3, 4), (_ : Int).toString)
res5: List[java.lang.String] = List(1, 2, 3, 4)

自分へのメモ:トニーに聞け!

4

3 に答える 3

23

あなたが遭遇しているのは、必ずしもCanBuildFromそれ自体、またはArray対のSeq問題ではありません。あなたは、高カインドStringではありませんmapが、そのChars に対してサポートしています。

SO: まず、Scala のコレクション設計について余談です。

必要なのは、コレクションの型 ( StringArray[Int]、 などList[Foo]) と要素の型 (上記Charに対応する 、Intなど) の両方を推測する方法です。Foo

Scala 2.10.x では、役立つ「型クラス」がいくつか追加されています。たとえば、次のことができます。

class FilterMapImpl[A, Repr](val r: GenTraversableLike[A, Repr]) {
  final def filterMap[B, That](f: A => Option[B])(implicit cbf: CanBuildFrom[Repr, B, That]): That =
    r.flatMap(f(_).toSeq)
 }
 implicit def filterMap[Repr, A](r: Repr)(implicit fr: IsTraversableOnce[Repr]): FilterMapImpl[fr.A,Repr] =
   new FilterMapImpl(fr.conversion(r))

ここに 2 つの部分があります。まず、コレクションを使用するクラスには、コレクションの特定Reprの型と要素の型の2 つの型パラメーターが必要Aです。

次に、コレクション型のみを受け取る暗黙的なメソッドを定義しますReprIsTraversableOnce(注: もあります) を使用してIsTraversableLike、そのコレクションの要素タイプを取得します。これは型シグネチャで使用されていることがわかりますFilterMapImpl[Repr, fr.A]

さて、これの一部は、Scala がその「ファンクターのような」操作のすべてに同じカテゴリーを使用しないためです。具体的にmapは、 に役立つメソッドですString。すべての文字を調整できます。ただし、StringできるのはSeq[Char]. を定義したい場合、カテゴリにはタイプと矢印Functorのみを含めることができます。このロジックは に取り込まれています。ただし、 aは であるため、のメソッドでサポートされているカテゴリでa を使用しようとすると、への呼び出しが変更されます。CharChar => CharCanBuildFromStringSeq[Char]mapSeqmapCanBuildFrommap

基本的に、カテゴリの「継承」関係を定義しています。パターンを使用しようとすると、Functor保持できる最も具体的なカテゴリに型シグネチャをドロップします。好きなように呼んでください。それが、現在のコレクション デザインの大きな動機となっています。

余談を終わらせて、質問に答えて

同時に多くの型を推論しようとしているので、このオプションは型注釈が最も少ないと思います:

import collection.generic._

def map[Repr](col: Repr)(implicit tr: IsTraversableLike[Repr]) = new {
  def apply[U, That](f: tr.A => U)(implicit cbf: CanBuildFrom[Repr, U, That]) = 
    tr.conversion(col) map f
}


scala> map("HI") apply (_ + 1 toChar )
warning: there were 2 feature warnings; re-run with -feature for details
res5: String = IJ

ここで注意すべき重要な点は、メソッドを使用できるようにするからへIsTraversableLikeの変換をキャプチャすることです。ReprTraversableLikemap

オプション 2

また、匿名関数を定義する前に、Scala が型Reprを推測できるように、メソッド呼び出しを少し分割します。U無名関数の型注釈を回避するには、すべての型が表示される前に認識されている必要があります。これで、Scala にいくつかの型を推論させることができますが、これを行うと暗黙的 に失われるものがあります。Traversable

import collection.generic._
import collection._
def map[Repr <: TraversableLike[A, Repr], A, U, That](col: Repr with TraversableLike[A,Repr])(f: A => U)(implicit cbf: CanBuildFrom[Repr, U, That]) = 
    col map f

を使用する必要があることに注意してくださいRepr with TraversableLike[A,Repr]。ほとんどの F-bounded 型は、このジャグリングを必要とするようです。

いずれにせよ、拡張されたもので何が起こるか見てみましょうTraversable:

scala> map(List(40,41))(_ + 1 toChar )
warning: there were 1 feature warnings; re-run with -feature for details
res8: List[Char] = List(), *)

それは素晴らしいことです。ただし、 と を同じように使用する場合はArrayStringもう少し作業が必要です。

scala> map(Array('H', 'I'): IndexedSeq[Char])(_ + 1 toChar)(breakOut): Array[Char]
warning: there were 1 feature warnings; re-run with -feature for details
res14: Array[Char] = Array(I, J)

scala> map("HI": Seq[Char])(_ + 1 toChar)(breakOut) : String
warning: there were 1 feature warnings; re-run with -feature for details
res11: String = IJ

この使用法には 2 つの要素があります。

  1. String/ ArraySeq/からの暗黙的な変換には、型注釈を使用する必要がありますIndexedSeq
  2. breakOutforを使用しCanBuildFrom、期待される戻り値に型注釈を付ける必要があります。

これは、暗黙的な変換を使用するため、型にorRepr <: TraversableLike[A,Repr]が含まれていないためです。StringArray

オプション 3

最後にすべての Implicit をまとめて配置し、ユーザーに型の注釈を要求することができます。最もエレガントな解決策ではないので、本当に見たくない場合を除き、投稿は避けたいと思います。

StringSO、基本的にコレクションとして含めたい場合はArray[T]、いくつかのフープをジャンプする必要があります。この map のカテゴリ制限は、Scala のStringとファンクタの両方に適用されます。BitSet

それが役立つことを願っています。他にご不明な点がございましたら、お問い合わせください。

于 2012-08-31T13:53:40.263 に答える
11

そこには実際にいくつかの質問があります...

最後の試行から始めましょう:

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)
 (implicit cbf: CanBuildFrom[Traversable[T], U, CC[U]]): CC[U] = 
  cct.map(t => f(t))(cbf)

CanBuildFrom[Traversable[Int], String, List[String]]これはコンパイルされますが、型の署名によると、スコープ内の暗黙的なものを探す必要があり、それがないため、機能しません。手動で作成する場合は、うまくいきます。

今、以前の試み:

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)
 (implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
  cct.map(t => f(t))(cbf)
<console>:10: error: type mismatch;
 found   : scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]]
 required: scala.collection.generic.CanBuildFrom[Traversable[T],U,CC[U]]
       cct.map(t => f(t))(cbf)
                          ^

CanBuildFrom暗黙の inがasコレクションTraversableのみを受け入れるようにハードコードされているため、これはコンパイルされません。ただし、他の回答で指摘されているように、実際のコレクション型 (2 番目の型パラメーター) を知っているため、適切に定義され、誰もが満足しています。実際には、からこのメソッドを継承しているため、これはさらに一般的です。TraversableFromTraversableLikemapCanBuildFrom[CC[T], U, CC[U]]TraversableLikemapscala.collection.generic.FilterMonadic

scala> import scala.collection.generic._
import scala.collection.generic._

scala> def map[T, U, CC[T] <: FilterMonadic[T, CC[T]]](cct: CC[T], f: T => U)
 |  (implicit cbf:  CanBuildFrom[CC[T], U, CC[U]]): CC[U] = cct.map(f)
warning: there were 1 feature warnings; re-run with -feature for details
map: [T, U, CC[T] <: scala.collection.generic.FilterMonadic[T,CC[T]]](cct: CC[T], f: T => U)(implicit cbf: scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]])CC[U]

scala> map(List(1,2,3,4), (_:Int).toString + "k")
res0: List[String] = List(1k, 2k, 3k, 4k)

最後に、 は ではないため、上記は配列では機能しませArrayFilterMonadicArrayしかし、 からへの暗黙の変換がArrayOpsあり、後者は を実装していFilterMonadicます。したがって、そこにバインドされたビューを追加すると、配列に対しても機能するものが得られます。

scala> import scala.collection.generic._
import scala.collection.generic._

scala> def map[T, U, CC[T]](cct: CC[T], f: T => U)
 |  (implicit cbf:  CanBuildFrom[CC[T], U, CC[U]], 
 |   ev: CC[T] => FilterMonadic[T,CC[T]]): CC[U] = cct.map(f)
warning: there were 1 feature warnings; re-run with -feature for details
map: [T, U, CC[T]](cct: CC[T], f: T => U)(implicit cbf: scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]], implicit ev: CC[T] => scala.collection.generic.FilterMonadic[T,CC[T]])CC[U]

scala> map(List(1,2,3,4), (_:Int).toString + "k")
res0: List[String] = List(1k, 2k, 3k, 4k)

scala> map(Array(1,2,3,4), (_:Int).toString + "k")
res1: Array[String] = Array(1k, 2k, 3k, 4k)

編集:および共同 で機能させる方法もありますString: 中間の 3 番目のものを使用して、入力/出力コレクションの上位の種類を削除するだけです:

def map[T, U, From, To, Middle](cct: From, f: T => U)
 (implicit ev: From => FilterMonadic[T, Middle], 
  cbf: CanBuildFrom[Middle,U,To]): To = cct.map(f)

これは、次Stringの場合でも機能しMap[A,B]ます。

scala> map(Array(42,1,2), (_:Int).toString)
res0: Array[java.lang.String] = Array(42, 1, 2)

scala> map(List(42,1,2), (_:Int).toString)
res1: List[java.lang.String] = List(42, 1, 2)

scala> map("abcdef", (x: Char) => (x + 1).toChar)
res2: String = bcdefg

scala> map(Map(1 -> "a", 2 -> "b", 42 -> "hi!"), (a:(Int, String)) => (a._2, a._1))
res5: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, hi! -> 42)

2.9.2 でテスト済み。しかし jsuereth が指摘したように、IsTraversableLike2.10 にはこれにより適した素晴らしい機能があります。

于 2012-08-30T23:05:22.433 に答える
8

これでしょうか?

def map[A,B,T[X] <: TraversableLike[X,T[X]]]
  (xs: T[A])(f: A => B)(implicit cbf: CanBuildFrom[T[A],B,T[B]]): T[B] = xs.map(f)

map(List(1,2,3))(_.toString)
// List[String] = List(1, 2, 3)

この質問も参照してください。

于 2012-08-30T20:57:57.523 に答える