2011年9月3日土曜日

ScalaのAPIとプレースホルダー構文の関係についてわかったことをまとめてみた

Scalaのコレクションクラスなどで利用することができるプレースホルダ(「_」)について、ちょっと調べてみたのでまとめてみました。

  1. 簡単なサンプル
  2. 引数を2つ受け取る場合
  3. 型の異なる2つの引数を受け取る場合
  4. 外で作った関数をコレクションのメソッドに渡す場合

まずはfilterメソッドによる簡単なサンプル

def filter (p: (A) ⇒ Boolean): List[A]

省略しないと
val list = (1 to 10).toList
println(list.filter((i:Int) => (i % 2 == 0)))

省略すると
val list = (1 to 10).toList
println(list.filter(_ % 2 == 0))

クラスのリストでも同じことができます。

case class Something(id:Int, something:String)
object Hello extends App {
val list = Something(1, "test") :: Something(2, "past") :: Something(3, "future") :: Nil
println(list.filter(_.id % 2 == 0))
}

filterメソッドのようにメソッドに渡す関数が引数をひとつしか受けない関数を前提としている場合、プレースホルダを2回利用することはできません。
下記のサンプルはコンパイルエラーになります。

val list3 = (1, 2) :: (3, 4) :: (5, 6) :: (7, 8) ::Nil
println(list3.filter((_._1 * _._2) % 3 == 0))

このような場合は、必ず
val list3 = (1, 2) :: (3, 4) :: (5, 6) :: (7, 8) ::Nil
println(list3.filter(p => ((p._1 * p._2) % 3 == 0)))

という書き方をしなければなりません。

メソッドに渡す関数が引数を2つ受け取る場合、プレースホルダを2回利用することができます。

def sortWith (lt: (A, A) ⇒ Boolean): List[A]

これはリストのソートをする関数です。
まずは省略しない形で
val list = 1 :: 5 :: 10 :: 7 :: 9 :: 8 :: 6 :: 2 :: 3 :: 4 :: Nil
println(list.sortWith((a:Int, b:Int) => (a <= b)))

これをプレースホルダを利用して省略すると
val list = 1 :: 5 :: 10 :: 7 :: 9 :: 8 :: 6 :: 2 :: 3 :: 4 :: Nil
println(list.sortWith(_ <= _))

これもListの要素がクラスになっていても利用できます。
case class Something(id:Int, something:String)
object Hello extends App {
val list = Something(2, "past") :: Something(1, "test") :: Something(3, "future") :: Nil
println(list.sortWith(_.id <= _.id))
}

2つの引数のうち一つがコレクションの要素でないものもあるので、注意が必要です。

def foldLeft [B] (z: B)(f: (B, A) ⇒ B): B

このメソッドを省略しない形で実行すると
val list = (1 to 10).toList
println(list.foldLeft[Int](0)((b:Int, a:Int) => (b + a)))

となります。
そして、省略すると
val list = (1 to 10).toList
println(list.foldLeft[Int](0)(_ + _))

となります。こういった関数の場合、コレクションの要素がクラスになっていると
case class Something(id:Int, something:String)
object Hello extends App {
val list = Something(2, "past") :: Something(1, "test") :: Something(3, "future") :: Nil
println(list.foldLeft[Int](0)((b:Int, a:Something) => (b + a.id)))
}

となり、1番目の引数と2番目の引数の型が違っているので、省略するときも注意が必要です。
case class Something(id:Int, something:String)
object Hello extends App {
val list = Something(2, "past") :: Something(1, "test") :: Something(3, "future") :: Nil
println(list.foldLeft(0)(_ + _.id))
}

APIをみたときは「A = コレクションの要素の型」、「B = その他の型」となると覚えておくと良いです。

最後に外でメソッドを作って、それを渡す場合、引数の型がコレクション要素の型となってい必要があります。

def foreach (f: (A) ⇒ Unit): Unit
</pre>
このような関数は、コンパイルに失敗します。
<pre class="prettyprint linenums">class Profile(n:String) {
val name = n;
def printName = {
println(name)
}
}
object Hello extends App {
val list4 = new Profile("Michael") :: new Profile("Jenifer") :: Nil
list4.foreach(println(_.name)) // ← ここで失敗!!
}

これは、foreachの関数がコレクションの要素の型を引数として受ける関数をパラメータとしているためです。
このような場合は
class Profile(n:String) {
val name = n;
def printName = {
println(name)
}
}
object Hello extends App {
val list4 = new Profile("Michael") :: new Profile("Jenifer") :: Nil
list4.foreach(p => println(p.name)) // ←
}

とする必要があります。

0 件のコメント:

コメントを投稿