S-JIS[2011-07-18/2017-01-29] 変更履歴
Scalaの「関数」についてのメモ。
C言語にはクラスという概念が無いので、定義できるのは全て「関数」(と呼ぶ)。
Javaではクラス内にしか定義できないので、全て「メソッド」。
Scalaは関数型言語なので、defで定義されるものを「関数」と呼ぶ…かと言うと、そうでもない。
Scalaにはオブジェクト指向言語の特徴も含まれているので、クラス(やトレイト)内で定義されたdefは「メソッド」と呼ばれることも多いようだ。
自分は、使い方に応じて「メソッド」と「関数」を呼び分けている。
classやtraitの直下でdefで定義され、呼び出し時に「インスタンス.
」(「this.
」「super.
」やthis省略も含む)を付けて呼び出すものは「メソッド」。
そうでないものは概ね「関数」。
Scala内部では(JavaVM上で稼動することもあって)全ての関数はメソッドとして定義されるが、表面上classやtraitに属していないものを(自分は)関数と呼ぶことにしている。
例えばPredefで定義されているメソッドや、objectからメソッドをインポートして使うもの、コンパニオンオブジェクト・パッケージオブジェクトで定義されている
メソッド、defの中で定義されているdef(ネストされたdef)等。
ちょっと困るのは、DSLとして(新しい文法のように)使う形式になっているもの。
例えば「breakable{ 〜
break }
」のbreakableやbreak。これは「ブロック」や「文」という呼び方でいいかなぁ?
実体はdefで定義されているメソッドで、それをインポートして使う形式なんだけど。
また、def以外で関数を表現する方法(関数リテラル)が存在する。
Scalaでは関数は第一級オブジェクト(first-class object)である。
つまり、関数をオブジェクトとして扱える。
したがって、関数(関数オブジェクト)を
といったことが出来る。
関数を受け取ったり関数を返したりするメソッド(関数)の事を「高階メソッド」「高階関数(higher order functions)」と呼ぶ。
関数を表す型は、引数の型を丸括弧で囲み、「=>」をはさんで戻り値の型を書いたもの。
defによる関数定義の順番と似ている。
def 関数名(引数名:引数の型, …) : 戻り型 = 本体
(引数の型, …) => 戻り型
例 | 備考 |
---|---|
(Int, Long) => Double |
引数がIntとLongで、Doubleを返す。 |
(Int) => Long |
引数がIntのみで、Longを返す。 引数が1個だけの場合は、丸括弧を省略することが出来る。 |
() => String |
引数が無く、Stringを返す。 |
=> String |
丸括弧が無いのは、関数の型ではなく、名前渡し。 |
Int => Int
scala> var f : ((Int) => Int) = null f: (Int) => Int = null scala> var f : (Int => Int) = null f: (Int) => Int = null scala> var f : (Int) => Int = null f: (Int) => Int = null scala> var f : Int => Int = null f: (Int) => Int = null scala> var f : Int => Int = (n:Int)=>{ n+1 } f: (Int) => Int = <function1> |
関数を代入できる変数を宣言する例。 |
Int => Int
scala> def test(f:(Int) => Int) = f(123) test: (f: (Int) => Int)Int scala> def test(f:Int => Int) = f(123) test: (f: (Int) => Int)Int scala> test((n:Int) => { n + 1 }) res51: Int = 124 scala> def foo(n:Int) = n + 1 foo: (n: Int)Int scala> test(foo) res52: Int = 124 |
関数を代入できる引数の例。 |
関数(メソッド)をdefで定義せずに、直接記述することが出来る。丸括弧内に引数名と型を書き、「=>」の後ろに式を書く。
defによる関数定義や関数の型の順番と似ている。
def 関数名(引数名:引数の型, …) : 戻り型 = 本体 (引数の型, …) => 戻り型
(引数名:引数の型, …) => 本体 : 戻り型 (引数名:引数の型, …) => 本体
なお、defでは引数リストを複数持つことが出来るが、関数リテラルではそういう書き方は出来ない。
scala> (a:Int, b:Int) => a+b
res3: (Int, Int) => Int = <function2>
scala> (a:Int)(b:Int) => a+b
<console>:1: error: not a legal formal parameter
(a:Int)(b:Int) => a+b
^
Scalaには関数(関数オブジェクト)を表すトレイトがある。
引数の個数に応じてscala.Function0〜Function22という名前になっている。
関数の型や関数リテラルは、Functionトレイトを使った書き方の糖衣構文らしい。
糖衣構文 | Functionトレイト | 備考 |
---|---|---|
scala> var f: Int => Long = (n:Int) => n f: (Int) => Long = <function1> |
scala> var f: Function1[Int, Long] = (n:Int) => n f: (Int) => Long = <function1> |
関数の型の書き方の例。 |
scala> var f: Int=>Long = (n:Int) => n+1 f: (Int) => Long = <function1> |
scala> var f: Int=>Long = new Function1[Int, Long] { | def apply(n:Int): Long = n+1 | } f: (Int) => Long = <function1> |
関数リテラルの書き方の例。 |
scala> f(1) res4: Long = 2 scala> f.apply(1) res5: Long = 2 |
scala> f(1) res6: Long = 2 scala> f.apply(1) res7: Long = 2 |
どちらの呼び出し方も出来る。 |
※defで定義されたメソッドをFunctionに変換するには、「_」を使う。
引数の型がT1,T2,…で、戻り値の型をRとする。
トレイト | メソッド | 備考 | 例 | |||
---|---|---|---|---|---|---|
メソッド名 | 引数 | 戻り値の型 | ||||
Function0〜22 |
apply |
v1:T1, v2:T2, … |
R |
関数を実行する。 (applyメソッドはメソッド名を省略して呼び出せる) |
val f = (a:Int, b:Int) => a+b |
3 |
Function1 |
andThen[A] |
g: (R)=>A |
(T1)=>A |
関数合成。 「自分を実行した後にgを実行する関数」を返す。 gには自分を実行した結果が渡る。 |
val add10 = (n:Int) => n+10 |
22 |
Function1 |
compose[A] |
g: (A)=>T1 |
(A)=>R |
関数合成。 「gを実行してから自分を実行する関数」を返す。 gを実行した結果が自分に渡ってくる。 |
val add10 = (n:Int) => n+10 |
12 |
Function2〜22 |
curried |
T1=>T2=>…=>R |
カリー化する。 | val f = (a:Int, b:Int) => a+b |
3 | |
Function2〜22 |
tupled |
((T1, T2, …))=>R |
「タプルを引数にとる関数」を返す。 | val f = (a:Int, b:Int) => a+b |
3 |
コレクションのMapやListも関数トレイトをミックスインしている。したがって、関数の様に扱える。
例 | 備考 | |
---|---|---|
Map |
scala> val map = Map("abc"->1, "def"->2, "ghi"->3) map: scala.collection.immutable.Map[java.lang.String,Int] = Map(abc -> 1, def -> 2, ghi -> 3) scala> val f: String=>Int = map f: (String) => Int = Map(abc -> 1, def -> 2, ghi -> 3) scala> f("abc") res11: Int = 1 |
Map[K,V]に対し、K=>V |
Seq |
scala> val seq = Seq("abc", "def", "ghi") seq: Seq[java.lang.String] = List(abc, def, ghi) scala> val f: Int=>String = seq f: (Int) => String = List(abc, def, ghi) scala> f(1) res12: String = def |
Seq[A]に対し、Int=>A |
Set |
scala> val set = Set("abc", "def", "ghi") set: scala.collection.immutable.Set[java.lang.String] = Set(abc, def, ghi) scala> val f: String=>Boolean = set f: (String) => Boolean = Set(abc, def, ghi) scala> f("abc") res13: Boolean = true |
Set[A]に対し、A=>Boolean |
配列 |
scala> val arr = Array("abc", "def", "ghi") arr: Array[java.lang.String] = Array(abc, def, ghi) scala> val f: Int=>String = arr f: (Int) => String = WrappedArray(abc, def, ghi) scala> f(1) res14: String = def |
Array[A]に対し、Int=>A |
Scalaでは関数は第一級オブジェクト(first-class object)なので、変数に代入したりできる。
しかしdefで定義したメソッドは、(あくまでメソッドであって)関数オブジェクトではない。
メソッド名の後ろに「_」を付けると関数オブジェクトに変換される。(C言語で「&」を付けるとアドレスが取れる(ポインターになる)ようなもの?)
もしくは、関数の型であることが明示されている場所への代入は自動的に関数オブジェクトに変換される。
Scala | C言語 | 備考 |
---|---|---|
def func(n: Int): Long = { n+1 } val fp = func _ val fp: Int=>Long = func printf("%d\n", fp(1)) |
long func(int n) { return n+1; } long (*fp)(int) = &func; long (*fp)(int) = func; printf("%d\n", fp(1)); |
C言語では、関数定義で関数名部分を「(*関数名) 」にすると、関数ポインターが宣言できる。そして関数名の前に「&」を付けると関数のアドレスが取得でき、関数ポインターに代入できる。 (なお、関数名の場合は&を付けなくても関数のアドレスとして扱われる) Scalaでは、メソッド名(メソッド呼び出し)の後ろに「_」を付けると関数オブジェクトになる。 もしくは、代入先の型が関数型として明記されていれば、自動的に関数オブジェクトに変換される。 |
def call(f: Int=>Long) = { f(123) } call(func _) call(func) |
long call(long (*fp)(int)) { return fp(123); } call(&func); call(func); |
|
type FP = Int=>Long val fp2: FP = func |
typedef long (*FP)(int); FP fp2 = func; |
type(C言語のtypedef相当)を使って型の別名を定義する例。 |
scala> def f() = 123 f: ()Int scala> val g = f g: Int = 123 scala> val g = f _ g: () => Int = <function0> |
引数が0個の場合の例。 単なる「f」だとfが実行されるが、「f _」だと関数オブジェクトになる。 |
|
def add(a:Int, b:Int) = a+b val inc: Int=>Int = add(_, 1) val r = inc(99) |
Scalaでは、引数の一部分だけをパラメーター化した関数オブジェクトを作ることが出来る。 これを関数の部分適用(partially applied function)と呼ぶ。 |
|
def add(a:Int)(b:Int)(c:Int) = a+b+c scala> val add1 = add(1) _ add1: (Int) => (Int) => Int = <function1> scala> add1(2)(3) res21: Int = 6 scala> val add3 = add(1)(2) _ add3: (Int) => Int = <function1> scala> add3(4) res22: Int = 7 |
Scalaではdefで複数の引数リストを持つメソッドを定義することが出来る。 そして、引数リストを省略した関数オブジェクトを作ることが出来る。 (「_」が引数リスト部分を省略する記号のように見える) (これも関数の部分適用) |
|
scala> def f(a:Int, b:Int) = a+b f: (a: Int, b: Int)Int scala> val c = (f _).getClass c: java.lang.Class[_] = class $anonfun$1 scala> val g = c.newInstance. | asInstanceOf[(Int,Int)=>Int] g: (Int, Int) => Int = <function2> scala> g(1,2) res23: Int = 3 |
関数オブジェクトはオブジェクトなので、Classの取得も出来る。 |
defで定義するメソッドは複数の引数リストを持てる。
ひとつの引数リスト内に複数の引数があるとき、それを別々の(引数が1つずつの)引数リストに分割することをカリー化(currying)と呼んでいることが多いような気がする。
def f1(a:Int, b:Int) = a+b ↓ def f2(a:Int)(b:Int) = a+b
関数オブジェクト(Functionトレイト)にはカリー化を行うメソッドがある。
scala> def f(a:Int, b:Int) = a+b f: (a: Int, b: Int)Int
scala> val g = f _ //関数オブジェクト取得
g: (Int, Int) => Int = <function2>
scala> g(1,2)
res31: Int = 3
scala> g(1)(2)
<console>:10: error: not enough arguments for method apply: (v1: Int, v2: Int)Int in trait Function2.
Unspecified value parameter v2.
g(1)(2)
^
scala> val c = g.curried //カリー化
c: (Int) => (Int) => Int = <function1>
scala> c(1)(2)
res33: Int = 3
scala> c(1,2)
<console>:11: error: too many arguments for method apply: (v1: Int)(Int) => Int in trait Function1
c(1,2)
^
カリー化された関数の型を見てみると、「(a:Int)(b:Int)=>a+b
」に当たる部分の型は「(Int)
=> (Int) => Int
」になっている。
つまり引数aだけ指定して実行した結果(の型)は、「(Int)
=> Int
」になる。
これに引数bを指定すると最終的に「Int
」が返る。
scala> val d = c(1) d: (Int) => Int = <function1> scala> d(2) res37: Int = 3
自分で引数リストを複数に分けた時と、curriedを使ってカリー化した時では、型が異なる。
したがって、部分適用の書き方も異なる。
自分で引数リストを分割 | カリー化 | 備考 |
---|---|---|
scala> def f(a:Int)(b:Int) = a+b f: (a: Int)(b: Int)Int scala> val g = f(1) _ g: (Int) => Int = <function1> |
scala> val f = {(a:Int,b:Int)=>a+b}.curried f: (Int) => (Int) => Int = <function1> scala> val g = f(1) g: (Int) => Int = <function1> |
自分で分割した方はあくまでメソッドなので、部分適用するには「_」を付ける必要がある。 カリー化した方は関数を返す形になっているので、「_」を付ける必要が無い。 |
scala> val g = f(1) <console>:8: error: missing arguments for method f in object $iw; |
scala> val g = f(1) _ <console>:8: error: _ must follow method; cannot
follow (Int) => Int |
自分で分割した方は、部分適用する際に「_」を付けないとエラーになる。 カリー化した方は、逆に「_」を付けるとエラーになる。 エラーメッセージを見ると、「_」を付けられるのはメソッドに対してだけのようだ。 |
curriedを使ってカリー化した方は関数を返すような型になっている。
Wikipediaのカリー化によると、
カリー化とは「引数が元の関数の最初の引数で、残りの引数を取って結果を返す関数を返す」ようにする事らしい。
したがって、自分で引数リストを分割(したメソッドを定義)しただけでは
カリー化とは言わないのかもしれない。
Scalaには、Function1トレイトを継承したPartialFunction(部分関数。俗称:ぱふ)というトレイトがある。
何が“部分”かと言うと、引数の有効範囲が決まっているところ。
PartialFunctionには引数をチェックするメソッドがあって、これがtrueを返すときだけapplyがちゃんとした値を返すというルールになっている。
参考: ゆろよろさんのScalaのPartialFunctionが便利ですよ
例 | 備考 |
---|---|
val pf = new PartialFunction[String, Int] { def isDefinedAt(s:String) = s match { case "abc" => true case "def" => true case _ => false } def apply(s:String): Int = s match { case "abc" => 123 case "def" => 456 } } val s = "abc" if (pf.isDefinedAt(s)) pf(s) //pf.apply(s) |
"abc"と"def"のときだけ値を返す。 "abc"・"def"以外でisDefinedAtを呼ぶとfalseが返る。 "abc"・"def"以外で直接applyを呼ぶと、MatchErrorが発生する。 |
val pf = { case "abc" => 123 case "def" => 456 }: PartialFunction[String, Int] val pf: PartialFunction[String, Int] = { case "abc" => 123 case "def" => 456 } |
波括弧でcaseから始めると、PartialFunctionのリテラルになる。「{ case 〜
} 」といった形。isDefinedAtもapplyも自動的に作られる。 ただし型推論ではPartialFunctionになってくれないようなので、型を明示する必要がある。 |
case class Person(name: String, age: Int) val pf: PartialFunction[Person, Int] = _.name match { case "abc" => 123 case "def" => 456 } |
“部分関数の引数”のメソッドを呼び出した結果でマッチしたい場合は「_.メソッド match 」と書く。[2017-01-29] |
case class Person(name: String, age: Int) def getName(person: Person) = person.name val pf: PartialFunction[Person, Int] = person => getName(person) match { case "abc" => 123 case "def" => 456 } |
“部分関数の引数”を引数としてメソッドを呼び出した結果でマッチしたい場合は「引数名 =>
メソッド(引数名) match 」と書く。[2017-01-29] |
type =?>[A,B] = PartialFunction[A,B] val pf: String=?>Int = { case "abc" => 123 case "def" => 456 } |
typeでPartialFunctionの別名を「=?> 」のような感じにすると、「A =?>
B 」という書き方が出来るようになる。(「=> 」が通常の関数なので、似た感じの命名をすると分かりやすい)Scalaでは型引数でも個数が2つの場合は中置記法が出来るので、「 =?>[A,B] 」は「A =?> B 」と書ける。(なお、「 PartialFunction[A,B] 」自身でも「A PartialFunction B 」と書くことは出来る) |
例えばコレクションのcollectメソッドは、引数がPartialFunctionになっている。
collectメソッドは、isDefinedAtを呼んで、有効なときだけ変換した値を返す(無効な値は捨てる)もの。
scala> List("abc", "def", "ghi").collect(pf) res84: List[Int] = List(123, 456)
scala> List("abc", "def", "ghi").collect{ case "abc" => 123; case "def" => 456 } res85: List[Int] = List(123, 456)
また、Scala2.9では、部分関数をtry式のcatchに指定できる。[2011-10-02]
下記の表の中では、型引数はPartialFunction[T1,
R]とする。
例として、「val pf: PartialFunction[String, Int] = { case "abc"=>123; case
"zzz"=>456 }
」が定義されているものとする。
(親)トレイト | メソッド | 備考 | 例 | |||
---|---|---|---|---|---|---|
メソッド名 | 引数 | 戻り値の型 | ||||
Function1 |
apply |
v1:T1 |
R |
関数を実行する。 引数が有効範囲外のときはMatchErrorが発生する。 |
pf.apply("abc") |
123 |
PartialFunction |
isDefinedAt |
x:T1 |
Boolean |
引数が有効な範囲に収まっている場合、trueを返す。 | pf.isDefinedAt("abc") |
true false |
PartialFunction |
lift |
(T1)=>Option[R] |
「結果をOptionに入れて返す関数」を返す。 この関数は、引数が有効範囲外のときはNoneを返す。 |
val f = pf.lift |
Some(123) None |
|
pf.lift("abc") |
Some(123) | |||||
Function1 |
andThen[A] |
g: (R)=>A |
(T1)=>A |
関数合成。 「自分を実行した後にgを実行する関数」を返す。 gには自分を実行した結果が渡る。 |
val f = pf andThen{ _ * 2 } |
246 |
PartialFunction |
orElse[T,R2] |
g: PartialFunction[T,R2] |
PartialFunction[T,R2] |
関数合成。 「自分の有効範囲内なら自分を実行、そうでないならgを実行する部分関数」を返す。 |
val pf2: PartialFunction[String,Int] = { |
123 789 |
Function1 |
compose[A] |
g: (A)=>T1 |
(A)=>R |
関数合成。 「gを実行してから自分を実行する関数」を返す。 gを実行した結果が自分に渡ってくる。 |
val f = pf compose{ (s:String) => s*3 } |
456 |