S-JIS[2011-07-18/2017-01-29] 変更履歴

Scala 関数

Scalaの「関数」についてのメモ。


概要

C言語にはクラスという概念が無いので、定義できるのは全て「関数」(と呼ぶ)。
Javaではクラス内にしか定義できないので、全て「メソッド」。
Scalaは関数型言語なので、defで定義されるものを「関数」と呼ぶ…かと言うと、そうでもない。
Scalaにはオブジェクト指向言語の特徴も含まれているので、クラス(やトレイト)内で定義されたdefは「メソッド」と呼ばれることも多いようだ。

自分は、使い方に応じて「メソッド」と「関数」を呼び分けている。
classtraitの直下で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
引数が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
              ^

Functionトレイト

Scalaには関数(関数オブジェクト)を表すトレイトがある。
引数の個数に応じてscala.Function0Function22という名前になっている。

関数の型関数リテラルは、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に変換するには、「_」を使う


Functionトレイトのメソッド

引数の型がT1,T2,…で、戻り値の型をRとする。

トレイト メソッド 備考
メソッド名 引数 戻り値の型
Function0〜22 apply v1:T1, v2:T2, … R 関数を実行する。
applyメソッドはメソッド名を省略して呼び出せる
val f = (a:Int, b:Int) => a+b
f.apply(1,2)
f(1,2)
3
Function1 andThen[A] g: (R)=>A (T1)=>A 関数合成。
「自分を実行した後にgを実行する関数」を返す。
gには自分を実行した結果が渡る。
val add10 = (n:Int) => n+10
val mult2 = (n:Int) => n*2
val f = add10.andThen(mult2)
val f = add10 andThen mult2
f(1)
22
Function1 compose[A] g: (A)=>T1 (A)=>R 関数合成。
「gを実行してから自分を実行する関数」を返す。
gを実行した結果が自分に渡ってくる。
val add10 = (n:Int) => n+10
val mult2 = (n:Int) => n*2
val f = add10.compose(mult2)
val f = add10 compose mult2
f(1)
12
Function2〜22 curried   T1=>T2=>…=>R カリー化する。 val f = (a:Int, b:Int) => a+b
val g = f.curried
g(1)(2)
3
Function2〜22 tupled   ((T1, T2, …))=>R タプルを引数にとる関数」を返す。 val f = (a:Int, b:Int) => a+b
val g = f.tupled
val t = (1,2)
g(t)
3

MapやListも関数

コレクションの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;
follow this method with `_' if you want to treat it as a partially applied function
       val g = f(1)
                ^
scala> val g = f(1) _
<console>:8: error: _ must follow method; cannot follow (Int) => Int
       val g = f(1) _
                ^
自分で分割した方は、部分適用する際に「_」を付けないとエラーになる。
カリー化した方は、逆に「_」を付けるとエラーになる。
エラーメッセージを見ると、「_」を付けられるのはメソッドに対してだけのようだ。

curriedを使ってカリー化した方は関数を返すような型になっている。
Wikipediaのカリー化によると、 カリー化とは「引数が元の関数の最初の引数で、残りの引数を取って結果を返す関数を返す」ようにする事らしい。
したがって、自分で引数リストを分割(したメソッドを定義)しただけでは カリー化とは言わないのかもしれない。


部分関数(PartialFunction)

Scalaには、Function1トレイトを継承したPartialFunction(部分関数。俗称:ぱふというトレイトがある。
何が“部分”かと言うと、引数の有効範囲が決まっているところ。
PartialFunctionには引数をチェックするメソッドがあって、これがtrueを返すときだけapplyがちゃんとした値を返すというルールになっている。

参考: ゆろよろさんのScalaのPartialFunctionが便利ですよ

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トレイトのメソッド

下記の表の中では、型引数はPartialFunction[T1, R]とする。
例として、「val pf: PartialFunction[String, Int] = { case "abc"=>123; case "zzz"=>456 }」が定義されているものとする。

(親)トレイト メソッド 備考
メソッド名 引数 戻り値の型
Function1 apply v1:T1 R 関数を実行する。
引数が有効範囲外のときはMatchErrorが発生する。
pf.apply("abc")
pf("abc")
123
PartialFunction isDefinedAt x:T1 Boolean 引数が有効な範囲に収まっている場合、trueを返す。 pf.isDefinedAt("abc")
pf.isDefinedAt("ghi")
true
false
PartialFunction lift   (T1)=>Option[R] 「結果をOptionに入れて返す関数」を返す。
この関数は、引数が有効範囲外のときはNoneを返す。
val f = pf.lift
f("abc")
f("ghi")
Some(123)
None
pf.lift("abc") Some(123)
Function1 andThen[A] g: (R)=>A (T1)=>A 関数合成。
「自分を実行した後にgを実行する関数」を返す。
gには自分を実行した結果が渡る。
val f = pf andThen{ _ * 2 }
f("abc")
246
PartialFunction orElse[T,R2] g: PartialFunction[T,R2] PartialFunction[T,R2] 関数合成。
「自分の有効範囲内なら自分を実行、そうでないならgを実行する部分関数」を返す。
val pf2: PartialFunction[String,Int] = {
  case "ghi" => 789
}
val f = pf orElse pf2
f("abc")
f("ghi")
123
789
Function1 compose[A] g: (A)=>T1 (A)=>R 関数合成。
「gを実行してから自分を実行する関数」を返す。
gを実行した結果が自分に渡ってくる。
val f = pf compose{ (s:String) => s*3 }
f("z")
456

Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま