あるプログラマの日記

プログラマのメモ、出来事、考えたこと、勉強とかの雑記

デフォルト引数、名前付き引数

デフォルト引数

関数、メソッドの定義で引数にデフォルト値を指定できる。

  • デフォルト引数は引数の一部またはすべてに指定できる。
  • デフォルト引数は、関数、メソッドの定義の際に 引数名: 型 = デフォルト値 の形式で定義する。
  • 可変長引数と併用することはできない。
  • 呼び出し時に引数を省略すると定義したデフォルト値が使用される。
  • 引数全てにデフォルト値が定義されている場合で、全ての引数を省略してメソッドを呼び出す場合 () は省略できない。
scala> def foo(list: List[String], s: String = ",") = list.mkString(s)
foo: (list: List[String], s: String)String

scala>  val list = List("foo", "bar", "hoge")
list: List[java.lang.String] = List(foo, bar, hoge)

scala> foo(list)
res0: String = foo,bar,hoge

scala> foo(list, "-")
res1: String = foo-bar-hoge

scala> def foobar(a: String = "foo", b: String = "bar") = a + b
foobar: (a: String, b: String)java.lang.String

scala> foobar
<console>:9: error: missing arguments for method foobar in object $iw;
follow this method with `_' if you want to treat it as a partially applied function
       foobar
       ^

scala> foobar()
res3: java.lang.String = foobar

名前付き引数

関数、メソッドの呼び出し時に引数名を指定して呼び出せる。

  • 呼び出し時に 引数名 = 値 の形式で指定する。
  • 名前付き引数で呼び出す場合は任意の順番に変えることができる。
  • 引数が多い場合は、名前付き引数を使用すると可読性が良くなる。
  • デフォルト引数と名前付き引数を組み合わせると引数の数の違いによるオーバーロードが不要になり可読性も良くなる。
scala> def bar(year: Int = 2012, month: Int = 4, day: Int= 7) = "%04d/%02d/%02d".format(year, month, day)
bar: (year: Int, month: Int, day: Int)String

scala> bar(month = 5, day = 5)
res4: String = 2012/05/05

scala> bar()
res5: String = 2012/04/07

scala> bar(day = 10)
res6: String = 2012/04/10

scala>  bar(month = 8)
res7: String = 2012/08/07

scala> bar(month = 7, day = 1, year = 2014)
res8: String = 2014/07/01

scala>  bar(2014, month = 7)
res9: String = 2014/07/07

scala>  bar(2014, month = 7, 2)
res10: String = 2014/07/02

リストバッファ

ListBuffer は mutable (変更可能) な List。
頻繁に追加、変更する場合は List だと新規に List を作成し直すため処理効率が
低下するので、そんな場合に ListBuffer を使用します。

ただし Scala は ListBuffer を暗黙には import していないので、使用するときは、scala.collection.mutable.ListBuffer を import しないと使えません。*1

scala> val foo = ListBuffer(1,2,3,4,5)
<console>:7: error: not found: value ListBuffer
       val foo = ListBuffer(1,2,3,4,5)
                 ^

scala> import scala.collection.mutable.ListBuffer
import scala.collection.mutable.ListBuffer

scala> val foo = ListBuffer(1,2,3,4,5)
foo: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3, 4, 5)

末尾に要素を追加する場合は + で追加。戻り値で追加後の ListBuffer を返します。
戻り値が不要な場合は += で要素を追加します。

scala> foo += 6
res0: foo.type = ListBuffer(1, 2, 3, 4, 5, 6)

要素の参照は List と同じで ListBufferオブジェクトに (..) で添字を指定します。

scala> foo(5)
res1: Int = 6

先頭に要素を追加する場合は +: で右側に追加先のListBufferのオブジェクトを指定し、+: の左側に追加要素を指定します。

scala> 0 +: foo
res2: scala.collection.mutable.ListBuffer[Int] = ListBuffer(0, 1, 2, 3, 4, 5, 6)

foo の先頭に要素を追加するには +=: を使用します。

scala> 0 +=: foo
res3: foo.type = ListBuffer(0, 1, 2, 3, 4, 5, 6)

List を末尾に追加する場合は ++= を使用します。

scala> foo ++= List(7,8,9)
res4: foo.type = ListBuffer(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

append は += と同じように末尾に要素を追加します。

scala> foo.append(11, 12)

scala> foo.toList
res5: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

prepend は +=: と同じように先頭に要素を追加します。

scala>  foo.prepend(-2, -1)

scala> foo.toList
res6: List[Int] = List(-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

List を先頭に追加する場合は ++=: を使用します。

scala> List(-4, -3) ++=: foo
res7: foo.type = ListBuffer(-4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

更新は、更新する添字を foo (..) に指定して = の後に変更する要素を指定します。

scala> foo(0) = 100

scala> foo.toList
res8: List[Int] = List(100, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

ListBuffer へ要素を追加するときは先頭でも末尾でも一定時間で処理してくれます。

*1:たぶん副作用のないプログラミングを推奨しているためでしょう。

List の使い方(続き5)

List の高階メソッド ( exists, forall, sortWith, foldLeft, foldRight )

特定の条件に合致する要素があるかを判定する

リストの要素中に条件に合致する要素があるかどうかを判断する。
要素を引数にとりBooleanを返す述語関数を引数に指定する。

  • 要素中に1つでも条件に合致するものがあるかを判定する。( exists )
scala> val list = List(2, 3, 4, 5, 6, 7, 8)
list: List[Int] = List(2, 3, 4, 5, 6, 7, 8)

scala> list exists { _ == 9 }
res0: Boolean = false

scala> list exists { _ == 5 }
res1: Boolean = true
  • すべての要素が条件に合致するかを判定する。( forall )
scala> list forall { _ < 9 }
res2: Boolean = true

scala> list forall { _ % 2 == 0 }
res3: Boolean = false

要素のソート

前後の要素 2つを引数にとり前後関係を判定して Boolean を返す関数を sortWith の引数に指定する。

scala> val list2 = List(3, 4, 2, 6, 7, 5, 1)
list2: List[Int] = List(3, 4, 2, 6, 7, 5, 1)

scala> list2 sortWith { _ < _ }
res4: List[Int] = List(1, 2, 3, 4, 5, 6, 7)

scala> list2 sortWith { _ > _ }
res5: List[Int] = List(7, 6, 5, 4, 3, 2, 1)

畳み込み

リストのすべての要素を結合する。(foldLeft, foldRight)
foldLeft と foldRight は引数リストを2つとる高階メソッド
1つ目の引数リストに結合前の初期値を指定する。
2つ目の引数リストに関数オブジェクトをとる。
関数オブジェクトは2つの引数をとり1つ目の引数リストの引数と同じ型の値を返す。
foldLeft は左の要素から順に処理する。
foldRight は右の要素から順に処理する。

scala> val list3 = List(1, 2, 3, 4, 5)
list3: List[Int] = List(1, 2, 3, 4, 5)

scala> list3.foldLeft(0)(_ + _)
res4: Int = 15

scala> list3.foldRight(0)(_ + _)
res5: Int = 15

1つ目の引数リストの引数に文字列を指定すると
畳み込みの要素と処理(+)は文字列として扱われる。

scala> list3.foldLeft("L")(_ + _)
res6: java.lang.String = L12345

scala> list3.foldRight("R")(_ + _)
res7: java.lang.String = 12345R

/: は foldLeft と同じ意味になる。メソッドの最後が ":" になるので右側にあるオブジェクトのメソッドになる。
:\ は foldRight と同じ意味のメソッド。

scala> (0 /: list3)( _ + _ )
res8: Int = 15

scala> ("L" /: list3)( _ + _ )
res9: java.lang.String = L12345

scala> (list3 :\ 0)( _ + _ )
res10: Int = 15

scala> (list3 :\ "R")( _ + _ )
res11: java.lang.String = 12345R

List の使い方(続き4)

List の高階メソッド ( foreach, map, filter, find, partition )

foreach

すでに、何度も使用している foreach はリストの各要素に対して、
引数で指定された関数を適用させる。
foreach に指定する引数は、1つの引数をとり戻り値が Unit(戻り値無し) の関数

scala> val foo = (n: Int) => println(n + 100)
foo: (Int) => Unit = <function1>

scala> List(5, 6, 7).foreach(foo)
105
106
107

List(5, 6, 7).foreach(foo) は List(5, 6, 7).foreach(e =>foo(e)) の簡易な書き方

map

リストの各要素に対して、引数で指定された関数を適用させて、その結果を新しい
List として返す。引数で指定する関数は1つの引数をとり戻り値を返す関数

scala> List(5, 6, 7).map { e => e * 2 }
res0: List[Int] = List(10, 12, 14)

プレースホルダーを使うと

scala> List(5, 6, 7).map { _ * 2 }
res1: List[Int] = List(10, 12, 14)

filter

List[T] のT型の要素を引数にとる T => Boolean 型の関数を引数にとる。
関数の戻り値が true になるすべての要素の List を返す。
特定項目の抽出に使用。

scala> List(1, 2, 3, 4, 5, 6, 7).filter { _ % 2 == 0 }
res2: List[Int] = List(2, 4, 6)

find

filter と同じ形式の関数を引数にとる。 find の場合は関数の戻り値が
true になる最初の要素を持つ Option型の Some(x) を返す。
全ての要素が false の場合は None を返す。

scala> List(1, 2, 3, 4, 5, 6, 7).find { _ % 4 == 0 }
res3: Option[Int] = Some(4)

scala> List(1, 2, 3, 4, 5, 6, 7).find { _ > 8 }
res4: Option[Int] = None

partition

filter と同じ形式の関数を引数にとる。リストのペアを返す。
関数が true を返す要素のリストと関数が false を返す要素のリストのペアになる。

scala> List(1, 2, 3, 4, 5, 6, 7).partition { _ % 2 == 0 }
res5: (List[Int], List[Int]) = (List(2, 4, 6),List(1, 3, 5, 7))

部分適用関数、カリー化

部分適用関数

複数の引数をとる関数の引数の一部に値を指定し、残った引数を未適用にした状態の
部分適用関数を作成できる。

foo の3つの引数の内、真ん中の引数を未確定にして前後の引数を指定した
部分適用関数値を pa に代入。
pa は foo の真ん中の引数だけを指定する部分適用関数になる。
部分適用関数の未確定部分は、 _: 型 の形式で書く。

scala> def foo(a: Int, b: Int, c:Int) = a + b + c
foo: (a: Int, b: Int, c: Int)Int

scala> val pa = foo(4, _: Int, 6)
pa: Int => Int = <function1>

scala> pa(5)
res0: Int = 15

scala> foo(4, 5, 6)
res1: Int = 15

pa(5) は foo(4, 5, 6) を呼び出した場合と同じ結果を返す。

最初の引数の値に 7 を指定した部分適用関数

scala> val pb = foo(7, _: Int, _: Int)
pb: (Int, Int) => Int = <function2>

scala> pb(9, 10)
res2: Int = 26

scala> foo(7, 9, 10)
res3: Int = 26

部分適用関数とは直接関係ないが、3つの引数をとる元の foo を関数オブジェクトとして
変数に代入する場合は、引数リストをプレースホルダーのアンダースコア _ でまとめて指定できる。

scala> val bar = foo _
bar: (Int, Int, Int) => Int = <function3>

カリー化 (currying)

  • 1つの引数をとる複数の引数リストを持たせること
  • 言い換えると複数の引数をとる(1つの引数リストを持つ)関数を1つの引数をとる関数チェーンに変換すること

上の(3つの引数を取る1つの引数リストを持つ)関数 foo をカリー化した
(1つの引数を取る3つの引数リストを持つ)関数 foo3 を定義する。

scala> def foo3(a: Int)(b: Int)(c: Int) = a + b + c
foo3: (a: Int)(b: Int)(c: Int)Int

各引数リストの引数に、それぞれ引数を指定すると
3つの引数を取る1つの引数リストを持つ関数 foo を呼び出した時と同様に結果を返す。
結果は同じだが動作としては以下のようになる。

  1. 1つ目(左端)の引数リストから引数を適用して関数値を返す。
  2. 返された関数値は2つ目の引数リストから引数を適用して関数値を返す。
  3. さらに返された関数値は最後の引数リストから引数を適用して計算結果の値を返す。
scala> foo3(1)(2)(3)
res0: Int = 6

カリー化した関数に部分適用した関数を作成する。
1つ目の引数リストに引数を指定した部分適用関数

scala> val s1 = foo3(1) _
s1: Int => Int => Int = <function1>

scala> val s2 = s1(2)
s2: (Int) => Int = <function1>

scala> val s3 = s2(3)
s3: Int = 6


1つ目の引数リストの引数と2つ目の引数リストの引数を指定した部分適用関数

scala> val f2 = foo3(1)(2) _
f2: (Int) => Int = <function1>

scala> val f3 = f2(3)
f3: Int = 6

2つの整数と2つの整数を引数にとって整数を返す関数を引数に指定する。
3つ目の引数リストで渡される関数で処理した結果を返す。

scala> def foo4(a: Int)(b: Int)(f: (Int, Int) => Int) = f(a, b)
foo4: (a: Int)(b: Int)(f: (Int, Int) => Int)Int

1つ目の引数リストに整数 2 を指定した部分適用関数を作成する。
部分適用関数 ff1 へ2つ目の引数リストの引数に整数と3つ目の引数リストの引数に関数リテラルを渡す。
関数リテラルプレースホルダーを使用すると1つ目と2つ目の引数リストに指定した整数が適用される。

scala> val ff1 = foo4(2) _
ff1: (Int) => ((Int, Int) => Int) => Int = <function1>

scala> ff1(3) { _ + _ }
res1: Int = 5

scala> ff1(3) { _ * _ }
res2: Int = 6

関数オブジェクトを curried を使ってカリー化する。
curried は Function2〜22 トレイトのメソッド

scala> def foo(a: Int, b: Int, c:Int) = a + b + c
foo: (a: Int, b: Int, c: Int)Int

scala> val bar = foo _
bar: (Int, Int, Int) => Int = <function3>

scala> val foobar = bar.curried
foobar: (Int) => (Int) => (Int) => Int = <function1>

scala> foobar(3)(4)(5)
res0: Int = 12

ローカル関数、プレースホルダー

ローカル関数

関数定義のスコープ内に、関数を定義できる。
同じスコープ内からしか見えないローカル関数になる。
ローカル関数は外側の関数の引数や変数にアクセスできる。

scala> def foo(list: List[String], n:Int) = {
     |   def bar(s: String) = if (s contains n.toString) n else -1
     |   list.map { str => bar(str) }
     | }
foo: (list: List[String], n: Int)List[Int]

scala> foo(List("100","35","98","73"), 5)
res1: List[Int] = List(-1, 5, -1, -1)

scala>  foo(List("100","35","98","73"), 3)
res2: List[Int] = List(-1, 3, -1, 3)

関数 foo 内部に関数 bar を定義。関数 bar は foo の引数 n を自由変数
として取り込んだクロージャ

ローカル関数とは関係ないが、List の map は引数が 1つの Function1 型を
引数としてとり、リストの各要素に順次適用する。

プレースホルダー構文

関数内で使用する引数が引数の数以下で順序通りの使用で
関数への引数が関数のコードブロック中に1度しか使われない場合は
アンダースコア "_" を使用して、引数指定個所を _ に置き換えることができる。

scala> List(1,2,3,4,5).map { n => n.toString }
res3: List[java.lang.String] = List(1, 2, 3, 4, 5)

scala>  List(1,2,3,4,5).map { _.toString }
res4: List[java.lang.String] = List(1, 2, 3, 4, 5)

引数が一つで戻り値が Unit の関数を引数に指定する場合は
暗黙にメソッドの引数を Scala が補完してくれるので
完全に省略できる。

scala> List(1,2,3).foreach { n => println(n) }
1
2
3

scala> List(1,2,3).foreach { println(_) }
1
2
3

scala> List(1,2,3).foreach { println }
1
2
3


引数が2つある場合も、関数ブロック内で利用する引数が指定引数の数以下で
同じ順序であれば使用できる。

scala> val hoge: (Int, Int) => Int = { _ * _ }
hoge: (Int, Int) => Int = <function2>

プレースホルダーを使用した関数リテラルの引数と戻り値の型が推論できない場合は、代入先の
変数に関数の型を指定する必要がある。

上の map や foreach の後が {..} になっているのは
関数の引数が1つの場合は、丸括弧 (..) を省略してかわりに {..} で囲んで書くことができる。

scala> def foobar(i: Int) = i * 2
foobar: (i: Int)Int

scala> foobar(2)
res7: Int = 4

scala> foobar({4})
res8: Int = 8

scala> foobar{6}
res9: Int = 12

引数の事前条件チェック

コンストラクタの引数や関数の引数がある条件を満たしているかの
事前のチェックは、require を使用する方法がある。

scala> def foo(a: Double, b: Double) = { require(b != 0); a / b }
foo: (a: Double, b: Double)Double

scala> foo(5, 2)
res0: Double = 2.5

scala> foo(4, 0)
java.lang.IllegalArgumentException: requirement failed
        at scala.Predef$.require(Predef.scala:145)
..snip..

assert でもよいかもしれないが引数チェックということでは
IllegalArgumentException を投げる require が良さそう。

scala> def bar(a: Double, b: Double) = { assert(b != 0); a / b }
bar: (a: Double, b: Double)Double

scala> bar(7, 2)
res1: Double = 3.5

scala> bar(5, 0)
java.lang.AssertionError: assertion failed
        at scala.Predef$.assert(Predef.scala:89)
..snip..

assert と require は Predef で定義されている