Life is Really Short, Have Your Life!!

ござ先輩の主に技術的なメモ

Scalaの部分適用とカリー化

Guide to ScalaーScalaプログラミング入門

Guide to ScalaーScalaプログラミング入門

なんで関数をチェーンすると部分適用が要らなくなるのか、ピンと来ない・・・

引数リストが1個しか無い関数

ま、そりゃそうよね、っていう感じの呼び出し方。部分適用していることを意識しなくてはならないので、煩わしい。

object Test{

  //ABCの3つの引数をとる関数
  def sum(a:Int,b:Int,c:Int) = a + b + c

  def main(args: Array[String]) :Unit = {

    //呼び出しには部分適用が必須
    //デフォルト引数に0を入れるのと大差ないんじゃないの説
    sum(1,_:Int,_:Int)
    sum(1,2,_:Int)
    sum(1,2,3)
  } 
}

引数リストが複数ある関数

引数リストって複数持てるんだって。test(1)(2) っていう形で呼び出せる。結局部分適用しないと使えないのなら、デフォルト引数を当て込んでも変わらないし、メリットが今ひとつ見えてこない。

object Test{

 //3つの引数リストを取る関数
 def sumMultiParamList(a:Int)(b:Int)(c:Int) = a + b + c

 def main(args: Array[String]) :Unit = {
    //これも部分適用が必要
    sumMultiParamList(100)(_:Int)(_:Int)
    sumMultiParamList(100)(50)(_:Int)
    sumMultiParamList(10)(20)(30)
 }
}

カリー化

ここまで来るとメリットが見えてくる。

言いたいことは分かった。複数の引数を取る関数を単一の引数をチェーンする関数に変換することだ。部分適用が不要になるし自分のほしい関数がケースバイケースで手に入るから便利だという所までわかった。

scala> def sumCurry(a:Int) = (b:Int) => (c:Int) => a + b + c
sumCurry: (a: Int)Int => (Int => Int)

scala> val curry_sum = sumCurry _
curry_sum: Int => (Int => (Int => Int)) = <function1>

scala> curry_sum(10)
res0: Int => (Int => Int) = <function1>

scala> curry_sum(10)(20)
res1: Int => Int = <function1>

scala> curry_sum(10)(20)(30)
res2: Int = 60

カリー化した関数を関数オブジェクトにしてみたら「Int => (Int => (Int => Int))」という関数オブジェクトが返ってきた。これだけ見てもサッパリわからんので、引数を何個か入れてどういうオブジェクトが返ってくるのかを見てみることにした。

第1引数のみだと、関数が返ってくる。1つ目の引数を与えて、残りの2つの引数を取って加算した結果を1つ目の引数に加えて返す関数オブジェクトが返ってくる。

第2引数まで指定しても、関数が返ってくる。2つの引数を与えて、残りの1つの引数を取って加算した結果を加えて返す関数オブジェクトが返ってくる。

第3引数まで指定すると、整数値が返ってくる。

日本語にするとサッパリわからへん。どうしたもんかと思ってたらカリー化された過程を書いてくれる方がいた。これが見たかった・・・!

daybreaksnow.hatenablog.jp

詳しくは上記を読むだけなのだが、自分の理解を深めるために写経する。

//冗長な書き方をしているカリー化
//return文を省略し、戻り値の型も省略
//curry(10)でinnerBが戻り値になる
//curry(10)(20)でinnerCが戻り値になる
//curry(10)(20)(30)で加算結果が戻り値になる
def curry(a:Int) = {
 def innerB(b:Int) = {
    def innerC(c:Int) = {
      a + b + c
    }
    innerC _
  }
  innerB _  
}

これを無名関数に置換していくとdefが不要になる。innerCを無名関数にするには関数リテラルを使えばいい。innerCはintを引数にa+b+cを返すのだから、(c:Int) => a + b + c になるわけだ。

def curry(a:Int) = {
 def innerB(b:Int) = {
      (c:Int) => a + b + c
  }
  innerB _  
}

同じ考え方でinnerBを置換する。innerBはIntを引数にとって、cを引数に取る関数を実行し、a+b+cの結果を返すという関数になる

def curry(a:Int) = {
    (b:Int) => (c:Int) => a + b + c
}

ここまでくれば{ }を取るだけ

def curry(a:Int) =  (b:Int) => (c:Int) => a + b + c

関数をネストすると部分適用が省略できる理由がピンと来ない...

curry(10)(_:Int)(_:Int)って書いても絶対動くし。

今日1日ですべてを把握するのは難しいかもなぁ。