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


scalaコップ本の第9章の続きをやりますよー、今回はループメソッドを使った単純化とかカリー化デス

クライアントコードの単純化

Scalaのコレクション型に定義されている特殊なループメソッドを使うとコードを単純化できることがよくあるらしいので、そのサンプルをやっていきますよーとのことです。この章ではexistsについて取り上げますよ

とりあえず次のようなリストに負の数が含まれているかどうかをチェックするメソッドを考えます

def containsNeg(nums:List[Int]):Boolean = {
  var exists = false
  for(num <- nums)
    // 負の数が含まれているようだったら結果をtrueにする
    if(num < 0)
      exists = true
  exists
}

じつは上のコードはコレクション型のexistsメソッドを使うことで置き換えることができるみたいですな

def containsNeg(nums:List[Int]) = nums.exists(_ < 0)

うん、めちゃめちゃスッキリしましたねー、とりあえず実行結果もつけてみますよ

scala> containsNeg(List(1,2,3))                            
res2: Boolean = false

scala> containsNeg(List(1,-2,3))
res3: Boolean = true
existsの存在

このexistsメソッドはコレクション(List, Set, Array, Map)で拡張されているIterableトレイトで定義されているらしいです。ようはScala組み込みの制御構文ではなく、Scalaのライブラリーが提供するコレクション型APIの公開メソッドであるとのことです。

…ライブラリが独自に定義した制御構造デス!くらいに理解しておけばいいかしら?

あとこのexistsは高階関数らしいです…確かに判定条件として関数式をわたせるからなぁ。

せっかくなのでサンプルをもう一個

Listに奇数が含まれているかどうかを判定するメソッドをexistsを使って実装しますよー

def containsOdd(nums:List[Int]) = nums.exists(_ % 2 == 1)

// 実行しますよー
scala> containsOdd(List(1,2,3))                                 
res4: Boolean = true

scala> containsOdd(List(2,4))  
res5: Boolean = false

うん、うまく動いてますねー

ちなみにexistsを使わない冗長な書き方は次のようになりますよー

def containsOdd(nums:List[Int]):Boolean = {
  var exists = false
  for(num <- nums)
    if(num % 2 == 1)
      exists = true
  exists
}

な、長いなぁ…

existsの仲間たち

existsのような特殊用途向けのループメソッドの多くは第3章で出てきているらしいので、あとで確認してみようかと…

まあ、車輪の再発明も程々に便利な仕組みは使いましょーってことで

カリー化

俺にカレーをk(ry

カリー化とはなんぞや?

コップ本によれば”1つの引数リストではなく、複数の引数リストに適用できる"関数をカリー化された関数と呼ぶみたいです…(´・ω・`)?よくわからんすな

とりあえず困った時のwikipediawikipedia:カリー化 してみたら余計わからなくなったので…とりあえずサンプルを見てみますかねー

まずはカリー化されていない関数を定義しますよー、処理的には2つの引数の和を求めるものですね

def plainOldSum(x:Int, y:Int) = x + y

// 実行しますよー
scala> plainOldSum(1,2)
res6: Int = 3

うん、ふつーですな。ではカリー化したサンプルを書いてみます。

def curriedSum(x:Int)(y:Int) = x + y

// 実行しますよ
scala> curriedSum(1)(2)
res7: Int = 3

(´ε`;)ウーン…引数の呼出かたが変わりましたねー。2つのInt型引数をとってたのが、Int型の引数1つずつを個別に取る形式になっております…何が違うかはわかったけどもメリットがいまいちわからんです

イマイチよくわからないので基本に戻る

こういう場合は基本の定義に戻ってみようの会、再びwikipediaを眺めてみるです

複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること。

うーん、日本語をうまく噛み砕けないので抽象式をみてみるですよ(関数fをカリー化したのがgですよ)

f: ( X * Y ) → Z ⇒ g: X → ( Y → Z )

複数のパラメータをバラバラにして関数処理で関数を処理する流れの中で別々に適用していくっていう感じになるのかしら?抽象式だけみてみれば複雑な式を(単純な)2つの式の組み合わせに落とし込める、、、みたいな雰囲気ですね

カリー化の中身を追って見る

イメージがいまいち作れないので、サンプルを続けていきますよー

どうやら上のcurriedSumは次のように置き換えることができるみたいです

def first(x:Int) = (y:Int) => x + y
val second = first(1)

// 実行
scala> second(2)
res8: Int = 3

内部的にクロージャーを作って1つ目の関数への参照を保持できる、っていうイメージでいいのかしら?(´・ω・`) … つかめるのはなんとなくの雰囲気だけだな

ちょっと違ったカリー化の置き換えをしてみる

コップ本の記述だけだとイマイチ理解がおっつかないので(´・ω・`)

上の例とは違って2つの関数には分けずに内部に関数式を収める形で書いてみますよー、どのみちクロージャーを使うことにはかわらないんですけども。

scala> def carriedSum(x:Int) = (y:Int) => x + y
carriedSum: (Int)(Int) => Int
// 実行結果も同じ
scala> carriedSum(1)(2)
res15: Int = 3

ちょっとみやすいようにブロックを明確にしてみますのよ

def carriedSum(x:Int) = {
  (y:Int) => x + y
}

うーん、こういうふうに見ると、関数の中に関数を収めてしまうことで引数を分散させるような書き方になるものをカリー化と言っていいのかしら?

プレースホルダーを利用してパラメータを固定してみる


カリー化された関数ではプレースホルダーを使うことでパラメータを固定できるみたいですねー、上で2つの式に分割したもののfirst(1)だけを渡すようなイメージですかね?

とりあえずやってみますね

2つ目のパラメータをプレースホルダーで置き換えますよ

// プレースホルダーで二つめのパラメータを置き換えます
scala> val onePlus = curriedSum(1)_
onePlus: (Int) => Int = <function>
// curriedSum(1)(2)の実行と同じになりますな
scala> onePlus(2)                  
res12: Int = 3

もういっちょ違う参照を使ってみますか

// 今度は1つ目のパラメータに2を指定しますよ
scala> val twoPlus = curriedSum(2)_
twoPlus: (Int) => Int = <function>
// curriedSum(2)(2)の実行と同じになりますな
scala> twoPlus(2)                  
res13: Int = 4

ちなみにこういう形で利用するプレースホルダーでは、コンパイラが判定できるそうなので_の前にはスペースいらないみたいです。

カリー化のメリットって?

いまいちメリットがわからんカリー化なんだけども、ようは上のように引数を固定した参照を簡単に作れるっていうのがメリットなのかも知れない…とここらへんとかをみてなんとなく理解した気になってみる。

だれか詳しい人教えてください(´・ω・`)

固定できるパラメーターってどこなのよ

もしもパラメータの固定がメリットだとしたら、どこを固定できるのかを覚えておいて損はないかも!という見切り発車をやってみますよ

curriedSumのパラメータを固定してみますよ

// まず2つ(意味なし)、まあこれじゃ結果値がでるわな
scala> val result = curriedSum(1)(2) 
result: Int = 3
// 上でやった例のとおりですよ
scala> val result = curriedSum(1)_  
result: (Int) => Int = <function>

//// 上の例の逆パターンをいろいろ試してみる
// ダメです
scala> val result = curriedSum_(2)
<console>:4: error: not found: value curriedSum_
       val result = curriedSum_(2)
                    ^
// ダメですねー
scala> val result = curriedSum _ (2)
<console>:1: error: ';' expected but '(' found.
       val result = curriedSum _ (2)
                                 ^
// ダメでしたー
scala> val result = curriedSum _(2) 
<console>:1: error: ';' expected but '(' found.
       val result = curriedSum _(2)

…固定できるのは第1引数だけみたいですなー

引数3つになったらどうなるの?

つい、出来心でやってしまおう。パラメータ固定ってどのくらいできんのかしら?

// 3つのパラメータを取るカリー化
scala> def hoge(x:Int)(y:Int)(z:Int) = x + y + z
hoge: (Int)(Int)(Int)Int
// 3つ固定で値取得
scala> val hoge1 = hoge(1)(2)(3)                
hoge1: Int = 6

// 末尾以外を固定…はOK
scala> val hoge2 = hoge(1)(2)_                  
hoge2: (Int) => Int = <function>
// 結果
scala> hoge2(3)                 
res17: Int = 6

// 末尾以外をプレースホルダーで置き換えしようとすると怒られるのでダメダメですなー
scala> val hoge2 = hoge(1)(_)_
<console>:5: error: missing parameter type for expanded function ((x$1) => (hoge(1)(x$1): (() => <empty>)))
       val hoge2 = hoge(1)(_)_
                           ^

scala> val hoge2 = hoge(1) _ _
<console>:1: error: ';' expected but '_' found.
       val hoge2 = hoge(1) _ _
                             ^

scala> val hoge2 = hoge(1)_ _ 
<console>:1: error: ';' expected but '_' found.
       val hoge2 = hoge(1)_ _
                            ^

うーん、どうやらカリー化したときのパラメータ固定は最後のパラメータ以外全部を固定しなきゃいけない様子(´・ω・`)

カリー化をされた関数を書き変えた形式で、一番内側の式をクロージャー的に変数を束縛した状態で渡せる、と解釈するのがよさそうね

def hoge(x:Int) = {
  (y:Int) => {
    // パラメータを固定してわたせるのは一番内側のこいつだけってことですね
    // xとyを束縛された状態でわたされますな
    (z:Int) => x + y + z
  }
}

***** あとから書いた ***************

_での省略方法が間違っていたっぽい、例えば引数を1つだけ固定して残り2つ指定しないで渡す場合は_を一つ書けばいいみたい

// 3パラメータのカリー化
scala> def hoge(x:Int)(y:Int)(z:Int) = x + y + z
hoge: (Int)(Int)(Int)Int

// 1つだけ固定しますよ
scala> val hoge1 = hoge(1)_                     
hoge1: (Int) => (Int) => Int = <function>
// 実行します
scala> hoge1(2)(3)                              
res27: Int = 6

// 1つも固定しませんよ
scala> val hoge2 = hoge _                       
hoge2: (Int) => (Int) => (Int) => Int = <function>
// 実行しますよ
scala> hoge2(1)(2)(3)    
res28: Int = 6

そういやプレースホルダーは引数全体を置き換えられるのでひとつだけ書けばいいんだよな(´・ω・`)

うん、これなら結構使えそうな気がする

***** あとから書いた ココマデ ***************

いじょー

とりあえずカリー化まで強引に…まだ完全に理解できていないのでこの後の節でおっついていければいいなぁ…と(´・ω・`)、まあ使っていかないと分からんだろうしね

次回は制御構造を自分で定義する話ですな、が、頑張ります