LL脳がscalaの勉強を始めたよ その23


Scalaコップ本の8章の続きですよ(゚∀゚)、今回は関数への部分適用をやりますよ

Macの再インストール祭りであまり時間が取れなかったのであまり進んでないですね(´・ω・`)

部分適用された関数

プレースホルダーが使えるのは引数単体だけじゃないんです

関数の引数として" _ "を使うことで引数表記を省略できる(超意訳)ってやつがプレースホルダーでしたが、こいつは単独の引数だけじゃなくて引数全体(パラメータリスト)にも適用できるよ(`・ω・´)って話です。

自然言語でダラダラ書いてもイメージが掴めないので直感的っぽいサンプルやりますよ

// 引数を3つ取る関数を定義します
scala> def sum(a:Int, b:Int, c:Int) = a + b + c
sum: (Int,Int,Int)Int
// 計算してみます
scala> sum(2,3,4)
res1: Int = 9

// プレースホルダーを使ってみますよー
// 3つの要素をもっているパラメータリストを _ で置き換えますね
scala> val sum1 = sum _                        
sum1: (Int, Int, Int) => Int = <function>
// きちんと計算できますな
scala> sum1(2,3,4)                             
res2: Int = 9

"_"の前にスペースを入れないとコンパイラ様が"sum_"という関数名と勘違いして怒られますね(´・ω・`)

scala> val sum99 = sum_
<console>:4: error: not found: value sum_
       val sum99 = sum_

Scalaでは引数を渡して関数を実行することを"関数を引数に適用する"というらしいんですが、上記のように" _ "を使って引数を置き換えたモノを”部分適用された関数”と呼ぶみたいですねー

部分適用の内部では何が起こっているのかしら?

関数の部分適用(って表現は正しいのかしら?)された場合の流れをそれっぽくまとめますねー

関数が部分適用されたときに、コンパイラが元々の関数式の定義から自動生成した引数(上の例だと3個の引数)をとる関数オブジェクトのインスタンスを生成しますよ

// sum1はインスタンスになりますな
scala> val sum1 = sum _                        
sum1: (Int, Int, Int) => Int = <function>

んで、生成されたインスタンスをさも元々の関数と同じように実行しているようにみえている下のような部分ですが

scala> sum1(1,2,3)
res14: Int = 6

実際は生成された関数オブジェクトがメンバとなるapplyメソッドを実行することで、元々定義された関数式を実行しておりますよーって動作っぽいです

scala> sum1.apply(1,2,3)
res15: Int = 6

コンパイラさんが頑張って元々の関数式のラッパーを作って実行するようなイメージになるんですかねー

さて部分適用はここからが本番だ(・∀・)

…と、ここまでゴチャゴチャやってはきたのですが、正直"部分適用された関数"の名前から最初に連想できたのは”引数の一部分だけを関数に適用”することなんですが、どうやらコレが実際できるみたいなのでやってみますよー

上のほうで出てきたみたいに、部分適用時にはラッパー的なオブジェクトが自動生成されるので、どの引数を部分適用するかを指定することでうまい具合にapplyメソッドを定義してくれるみたいデス

まあ、具体的なイメージを掴むためにサンプルやってみますよー

// 部分適用するのです
// 第2引数のみにリテラル(定数)を与えて引数を2つ取る関数オブジェクトを生成しますよ
scala> val sum2 = sum(_:Int, 3, _:Int) 
sum2: (Int, Int) => Int = <function>

// 実行してみます
scala> sum2(1,2)
res17: Int = 6

// 部分適用で引数が2つになっているので元(sum)のような呼び出しはできませんな
scala> sum2(1,2,3)
<console>:7: error: wrong number of arguments for method apply: (Int,Int)Int in trait Function2
       sum2(1,2,3)

うん、今まで引数調整のために複数のラッパーを書いていたような部分がかなりスッキリする気がしますねー

アンダースコアすら省略できる条件

”その関数式を記述する箇所が関数呼び出し必須である”という特殊条件的なものを満たすと" _ "すら省略できてしまいます

例えばforeachのパラメータとしての関数呼び出しで、こんな感じになりますよー

// コレクション(リスト)を定義
scala> val list1 = List(1,2,3) 
list1: List[Int] = List(1, 2, 3)

// プレースホルダー的 "_" が 
scala> list1.foreach(println _)
1
2
3
// 不要になりますよー
scala> list1.foreach(println)  
1
2
3

// 上の呼出はパラメータリストと言いつつも
// 引数一つしかとらないのでコレと同じ
scala> list1.foreach(println(_))
1
2
3
// 大元はこんな感じですねー
scala> list1.foreach(x => println(x))
1
2
3

当然、関数必須箇所以外で省略すると怒られますなー

// _ 書けよバカタレ(`・ω・´)と怒られました
scala> val sum3 = sum
<console>:5: error: missing arguments for method sum in object $iw;
follow this method with `_' if you want to treat it as a partially applied function
       val sum3 = sum
                  ^

" _ "が必要だったりするのはHaskellやMLなんかだと部分適用関数式が当たり前なんだけど、Javaなんかと折り合いをつけるために部分適用を明示する必要があったからとかなんとか。なので、”ココは関数”って箇所は省略できるようにしたらしいっすね…The 折衷案


こんな感じでScala全体に漂う折衷案的マインドは結構嫌いではないデス(・∀・)

いじょうー

次回くらいで8章終われないかな、終わりたいな…でも色々立て込んでるから難しいかな(´・ω・`)

まあ、途切れないように頑張っていきますよー