Scalaの醍醐味は関数型言語とオブジェクト指向言語のハイブリッドだ!
今回はオブジェクト指向をやっている人には馴染みが薄い、
関数型言語について語るよ。
関数型言語って何?
関数を駆使してプログラミングできる言語が、関数型言語だ!。
関数とはオブジェクト指向でいうと__「メソッド」__みたいなもんだ。
意味は違うけどね。数学的な例だと、
y = sin(x)
これは引数x
を与えると、sin
が計算して値をyに代入するんだ。
ここではsin(x)
が関数だよ。
次にScalaで関数を作ってみるよ。
object FunctionExample{
def main(args: Array[String]){
var calculate = (x: Int) => x + 5 : Int
printf("y=%s \n", calculate(1))
printf("y=%s \n", calculate(2))
}
}
実行すると以下のようになるよ。
$ scala FunctionExample.scala
y=6
y=7
FunctionExample.scalaで定義した関数は、calculate
。
引数に数値を与えると、その引数に5を足して返す関数だ。
これは、y=x+5
という一次関数を表してるんだよ。
誤解を恐れずに言うと、Cの関数やJavaのメソッド
と同様に__処理を表す部分__でもある。
もちろん違いはあるので、関数型言語の特徴と言う形で述べていこう。
関数型言語の特徴
- immutable (いみゅーたぶる)
- 副作用がない
- 関数はファーストクラス(第一級)
関数型言語の特徴を調べると、上記のような項目が必ず出てくる。
最初はよくわからないよね?
でも基本だから避けては通れないよ。
immutable(いみゅーたぶる)
immutableとはim(in) + mutableのことだ。
mutable?「変わる」ことだよ。immutable「変わらない」ことだね。
「変わる」って何が?変わるのは値だよ。
Scalaでこれを端的に表すキーワードが存在している。
var
とval
だ。
object Mutable{
def main(args: Array[String]){
var mutable = "Mutable"
printf("%s ", mutable)
mutable = "Change mutable"
printf("%s ", mutable)
}
}
実行すると、var
で定義した変数mutable
の値が切り替わることがわかるね。
これがmutableだ。
$ scala Mutable.scala
Mutable
Change mutable
次にimmutable。
object Immutable{
def main(args: Array[String]){
val immutable = "Immutable"
printf("%s ", immutable)
immutable = "Change immutable"
printf("%s ", immutable)
}
}
実行するとコンパイルエラーが発生する。
$ scala Immutable.scala
Immutable.scala:6: error: reassignment to val
immutable = "Change immutable"
^
one error found
immutable、即ちval
で定義した変数の値が変えられないことがわかったかな?
副作用がない
本来するべきことを作用とすれば、作用に伴って別のことをしてしまうのが副作用だ。
風邪をひいたらどうする?
風邪薬を飲むよね。飲んだら眠くなることない?
風邪薬では、風邪を治すことが作用、眠くなることが__副作用__だ。
副作用の具体例をソースで表してみるよ。
object SideEffect{
var total = 0
def main(args: Array[String]){
var add = (x:Int) => {
total+=x
total
}
printf("total=%s \n", add(1))
printf("total=%s \n", add(1))
printf("total=%s \n", add(1))
}
}
実行すると以下のようになる。
$ scala SideEffect.scala
total=1
total=2
total=3
この例では、メソッドadd
を引数1
で3回呼んでいるが、
呼ばれるたびにtotal
を足している。
そのため、add
は呼ばれるたびに異なる戻り値を返す。
add
はオブジェクトフィールドtotal
にも作用していることを、
副作用と言う。
副作用のない例は以下となる。
object NoneSideEffect{
def main(args: Array[String]){
var add = (x: Int) => x + 5
printf("add=%s \n", add(1))
printf("add=%s \n", add(1))
printf("add=%s \n", add(1))
}
}
$ scala NoneSideEffect.scala
add=6
add=6
add=6
SideEffect.scala
はオブジェクト指向ぽく書いたけど、
まさしくオブジェクト指向は副作用の塊だね。
もちろん関数型言語を駆使すれば、副作用を全て排除できるわけではないよ。
IOに関わる部分は副作用だからね。
でもなくせる場所では副作用をなくそうぜ!
関数はファーストクラス(第一級)
ファーストクラス(第一級)とは、関数を型として扱えるってことなんだ。
文字列型とか数値型とかと同じにみなせる。
型として扱えるということは、var add
みたいな変数に、関数を設定することが可能なんだよ。
JavaScriptのvar func = function(){......};
と同じだね。
次にファーストクラスの利点。
- 関数の引数として渡せる
- 関数の戻り値として返せる
Javaにはできないことだよ。メソッドだけをオブジェクトとして返せないからね。
メソッドはオブジェクトありきなんだ。
関数の戻り値の例で見てみよう。
object FirstClassFunction{
def main(args: Array[String]){
val add = (x: Int) => {
val _add = (y: Int) => y + 5
_add(x)
}
printf("add=%s \n", add(1))
printf("add=%s \n", add(2))
printf("add=%s \n", add(1))
}
}
実行してみよう!
$ scala SideEffect.scala
add=6
add=7
add=6
関数add
の戻り値はなんと関数_add
だ!
変数func
には、add()
の戻り値_add
が入っている。
そしてfunc(1)
は_add(1)
を評価することと同じになる。
だからfunc(1)
の結果は6になるのさ。
ちなみにファーストクラスのクラスは、オブジェクト指向のクラスとは
全く関係ないから注意してね!
最上級だ!!!
言語の翻訳ではよく「functions as first-class citizens」って言葉が出てきて
「第一級市民である関数」なんて訳されているよ。
関数とメソッドの違い
Java等のオブジェクト指向言語を使っていると、関数とメソッドの違いがわからないかもしれない。
メソッドはあくまでオブジェクトと関連付いているんだ。
オブジェクトの操作をメソッドと言う。
オブジェクトには状態(フィールド)があるんだけど、
状態によって戻り値が変わる。引数がなくても、
戻り値が変わることがあるんだ。
staticおじさんの作りしかしらなかったら、ゴメンナサイ!
一方関数は、引数に依存する。
引数が同じなら常に同じ戻り値を返し、
引数が変わったら別の戻り値を返す。
状態には依存させないよ。
文法上の違いもあるよ。
def
キーワードはメソッドになる。
def add(x:Int, y: Int) = x + y
これは関数リテラルで、関数となる。
var add = (x:Int, y:Int) => x + y
まとめ
今回は関数型言語について語ってみたけど難しかったかな?
実は僕も、関数型言語を理解するために日々悩んでいるんだ。
それだけ難しい概念だと思うけど、
ここを掌握できればプログラミングの質が上がると信じているよ。
今回の、関数型言語のソースを実際に動かすことで、
体で感じてくれたかな?