Set + Tuple = ?
この記事はScala Advent Calendar (http://atnd.org/events/10683)の27日のものです。
簡単な記事です。
さて、以下の式はどの様な結果を生成するでしょうか?
Set() + (1,2)
プログラマ(私)の意図としては、Set( (1,2) ) ((1,2)の1要素タプルを持つ集合)を作って欲しかった一文です。
しかしながら、この文は予想に反してSet(1,2) (1, 2を持つSet[Int]型の集合)を生成します。
なぜでしょうか?
その理由を知るためにSet型の+メソッドを見てみましょう。
すると、以下の様なオーバーロードが存在することが分かります。
def + (elem1: A, elem2: A, elems: A*) : Set[A]
def + (elem: A) : Set[A]http://www.scala-lang.org/api/current/scala/collection/Set.html
もうお分かりだとは思いますが、scalaの演算子はメソッド呼び出しの置き換えです。
その際、引数の()が1つ*1である場合は、ドットを省略出来るようです。(不確かなので、正確な情報を知っている方教えて下さい)
// 検証コード (1 to 10) sortWith (_>_) // OK (1 to 10) foldLeft (0) (_+_) // NG (1 to 10).foldLeft (0) (_+_) // OK
要するに、Set() + (1,2)は Set().+(1,2)に置き換えられており、その結果としてSet(1,2)が生成されていた訳です。
コレと同様のミスは型を指定しているコードではコンパイルエラーとなって現われます。
Setの要素として(Int, Int)を要求しているにも関わらず、メソッド呼び出しで2つのIntを与えているためにコンパイルエラーに成るわけです。
scala> val set = Set[(Int, Int)]() scala> set + (1,2) <console>:8: error: type mismatch; found : Int(1) required: (Int, Int) set + (1,2)
私が使っている限りでは、foldLeftなどのループ処理中にSet要素を追加したい事が結構あったので、そこでハマった現象です。
解決作としては、以下の方法が考えられます。
// 1) 一度タプルを変数に代入する scala> val v = (1,2) scala> Set() + v res33: scala.collection.immutable.Set[(Int, Int)] = Set((1,2)) // 2) タプルである事を更に括弧を付けて示す scala> Set() + ((1,2)) res34: scala.collection.immutable.Set[(Int, Int)] = Set((1,2)) // 3) 仮引数を使って括弧内の要素を分かり安くする scala> Set() + (elmt = (1,2)) res35: scala.collection.immutable.Set[(Int, Int)] = Set((1,2)) // 4) タプルの別表現を用いる // thanks id:xuweiさん scala> Set() + Tuple2(1, 2) scala> Set() + (1 -> 2) res36: scala.collection.immutable.Set[(Int, Int)] = Set((1,2))
私的には1)が良いのではないかと思います。
単純にタプルを渡すだけのメソッドでも、同様のミスが現われる可能性は大きいと言えます。
そのため、タプルをメソッドに渡す場合、一時的な変数を用意して利用する(1)のパターンを利用する)と安全でしょう。
追記:また、タプルか引数かの曖昧さを排除した4)も良いと思います。