Scalaでリストを連結するための:::
と++
の間に違いはありますか?
scala> List(1,2,3) ++ List(4,5)
res0: List[Int] = List(1, 2, 3, 4, 5)
scala> List(1,2,3) ::: List(4,5)
res1: List[Int] = List(1, 2, 3, 4, 5)
scala> res0 == res1
res2: Boolean = true
ドキュメント から、++
がより一般的であるのに対し、:::
はList
-specificです。後者は他の関数型言語で使用されているため提供されていますか?
レガシーリストはもともと機能的な言語に見えるように定義されていました:
1 :: 2 :: Nil // a list
list1 ::: list2 // concatenation of two lists
list match {
case head :: tail => "non-empty"
case Nil => "empty"
}
もちろん、Scalaは特別な方法で他のコレクションを進化させました。 2.8が出たとき、コレクションは最大限のコード再利用と一貫したAPIのために再設計されました、それであなたは++
を使ってany 2つのコレクションそしてイテレータさえも連結できます。しかし、Listは、廃止予定の1つか2つを除いて、元の演算子を保持するようになりました。
:::
はリストでのみ機能しますが、++
は任意のトラバース可能ファイルで使用できます。現在の実装(2.9.0)では、引数がList
でもある場合、++
は:::
にフォールバックします。
常に:::
を使用してください。理由は2つあります。効率と型の安全性です。
効率
x ::: y ::: z
は正しい結合であるため、x ++ y ++ z
は:::
より速いです。 x ::: y ::: z
はx ::: (y ::: z)
として解析されます。これは(x ::: y) ::: z
よりアルゴリズム的に高速です(後者はO(| x |)より多くのステップが必要です)。
タイプ安全
:::
では、2つのList
を連結することしかできません。 ++
を使えば、List
に任意のコレクションを追加できます。
scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)
++
は+
と混同するのも簡単です。
scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab
異なる点は、最初の文は次のように解析されるということです。
scala> List(1,2,3).++(List(4,5))
res0: List[Int] = List(1, 2, 3, 4, 5)
2番目の例は次のように解析されます。
scala> List(4,5).:::(List(1,2,3))
res1: List[Int] = List(1, 2, 3, 4, 5)
あなたがマクロを使っているのであれば、あなたは注意するべきです。
その上、2つのリストに対する++
は:::
を呼び出していますが、それはListからListへのビルダーを持つことを暗黙の値で要求しているためより多くのオーバーヘッドを伴います。しかし、マイクロベンチマークはその意味で有用なことは何も証明していません、私はコンパイラがそのような呼び出しを最適化すると思います。
ウォームアップ後のマイクロベンチマーク.
scala>def time(a: => Unit): Long = { val t = System.currentTimeMillis; a; System.currentTimeMillis - t}
scala>def average(a: () => Long) = (for(i<-1 to 100) yield a()).sum/100
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ++ List(e) } })
res1: Long = 46
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ::: List(e ) } })
res2: Long = 46
Daniel C. Sobraiが言ったように、++
を使用して任意のコレクションのコンテンツをリストに追加できますが、:::
ではリストを連結することしかできません。