Scala訳わかんね(1)
今Scalaを勉強してます。今まで手続き型言語(オブジェクト指向含む)をずっとやってきたので、関数型言語の考え方が全く違ってかなり難しい。でも、なんだかぼんやり分かってきました。
まだまだわからないことだらけなので、間違っている部分はコメントでどんどん突っ込んでください。
今読んでいるScalaの参考書はhttp://www.amazon.co.jp/Scala%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E5%85%A5%E9%96%80-%E3%83%87%E3%82%A4%E3%83%93%E3%83%83%E3%83%89%E3%83%BB%E3%83%9D%E3%83%A9%E3%83%83%E3%82%AF/dp/4822284239/です。
※ アフィリエイトじゃないので自由に踏んでください。
この参考書、Scalaの文法や機能に終始していて「関数型言語とはなんぞや?」という部分がないんですね。
ちなみに、今も分からないのは
「で、結局関数を使ってどうやって動くプログラムが動くの?」「ラムダ計算ってなに?」とか色々…
まだ分からないことだらけなんですけど、最初の山は越えた気がします。
関数・遅延評価・変数に代入できない…が繋がった!!
Scalaで関数とかメソッドとか出てくるんです。前述の本だと違いはほとんど説明されていません。オブジェクト脳で考えると、関数は処理(手続き)に名前を付けて定義したもので、メソッドはクラスに定義されたオブジェクトを操作するための関数でしょ、くらいの理解でした。
ただ、Scalaの関数は入力が同じ場合には、どんなタイミングで何回読んでも出力は同じという位は理解しています。
Scalaの関数定義とメソッド定義
val fact : (Int) => Int = (n:Int) => {
if (n == 1) 1 else n * fact(n - 1)
} (n:Int)以降が関数自体(関数オブジェクト)の定義ということはわかるんだけど、その前はいまいち分かってません("="とか":"とかの記号の意味が特に)。全体ではIntを引数に取りIntを返す関数factを定義するという感じです。上の例だと再帰呼び出しをしているので戻り値の型を省略できないのですが、戻り値の型を省略できる関数なら↓で定義できます。
val mul = (a:Int, b:Int) => a * b※ factには"{"〜"}"がありmulにはないですが、"{"〜"}"は複数行に渡る定義を纏めているだけです。1行で書けるならば省略可能です。
Scalaのメソッド定義
def fact(n:Int):Int = {
if (n == 1) 1 else n * fact(n - 1)
} ググると"def"は関数の定義に使うという説明もいっぱい出てくるのでちょっと自信ないですが…この二つ、呼び出せば当然同じ値が返ってきます。でもですね、関数はファーストオブジェクトなんです。変数に代入できる、引数としても戻り値としても使える。簡単に言うと数値や文字列と同じように扱えるんです。
簡単に言うと
(n:Int) => {
if (n == 1) 1 else n * fact(n - 1)
}や(a:Int, b:Int) => a * bは5とか"文字列"とかと本質的に意味は同じなのです。ですので、先ほどの
val mul = (a:Int, b:Int) => a * bはvar x = 5と意味は同じです。上は変更できない変数mulに引数a, bを取りa * bを返す関数を代入。下は変更できる変数xに5を代入です。
ここまではすんなり理解できました。ところがですね、このファーストオブジェクトとか変数への代入とかがもうオブジェクト脳だと気付きました。
valは変数ではないと考えるとしっくりきました。同じく関数もオブジェクトではないと考えるとしっくりきます。
valの意味
大抵のScalaの説明では従来の言語と比較して、valは変更できない変数varは変更できる変数を定義すると説明されています。で、valはJavaのfinalやC++のconstと同じですよと。でも、これ全然違いますよ。
finalやconstはプログラミングのミスを減らしたり楽したりするために導入したものです。
ところが、僕の理解ではvalは関数型言語に絶対に必要なものなのです。
どういうことかというと、式や関数をその言葉のままに理解すればいいんです。プログラミングを忘れて算数を思い浮かべると分かります。
算数や数学で証明問題とかで式とか関数とか使いますよね。あれです。あれ。
文系なので適当な例が思いつきませんが、
y = 2 * x … Aとおくとかx = 3の時、Aは2 * 3なので、
y = 2 * 3 = 6
関数 f(x) = 2 * x が定義されている時、ほにゃらら
関数 g(f(x))がほにゃらら
みたいなやつです。
この2 * xが式ですね。式に入力を与えて一意に出力が決まるものが関数です(多分)。上記のf(x)は関数です。僕の理解ではAも関数です(多分)。
そして、このAやf(x)がvalで付けた名前です。上記の例でAやf(x)に式を代入なんてしてないですね。関数に名前を付けているだけです。
そう、valというのは値や関数に名前(ラベル)を付けているだけなんです。C++なら#defineマクロと考えるとわかりやすいです。
無名関数なんて言葉がありますが、もともと関数には名前なんてないんです。問題を解くために便宜上名前を付けているだけ。5という数字や"文字列"という文字列に名前がないのと同じです。
今までの言語だと変数は値を入れる箱だというイメージがあったので、変更できない変数なんて捉え方をしていました。
valをラベルと考えると、遅延評価もしっくりきます。関数に付けられたラベルを他の関数の引数に与えたからって評価なんてしませんよね。上記のg(f(x))の部分では評価なんてしてません。そう、(僕の理解では)関数型言語ではデフォルトが遅延評価なのです。
※ あ、でも全ての引数に定数が与えられた時はその場で評価されてもいい気がしますね。ちょっと自信がなくなってきた…
そして、ラベルを付けているだけなので関数をオブジェクト化したりしません。
結論
曖昧な部分を残しつつ結論です。
varは変数、valはラベル。
valで関数にラベルを付けても本来評価はされない。(Scalaの実装は知らない)
varは変数なので関数の値を入れる変数なので評価される??
関数は入力を与えると一意に出力が決まる式で、元々名前はないが便宜上valでラベルを付けることが出来る。
関数は扱いやすいようにコンパイラではオブジェクトとして実装はされているが、関数は関数(の定義自体)でそれ以上でも以下でもない。なので、valで名前を付けたところで関数オブジェクトになるわけではなく評価されるわけでもない、関数は元の定義のまま。あくまでもコンパイラの実装でオブジェクトにしているだけ。