CLOVER🍀

That was when it all began.

カリー化の短縮表記を通常の表記で書いてみる

Scalaは関数を返す関数…いわゆるカリー化をサポートしていて、その例としてC#から拝借したusingの定義がよく挙がっています。

要は、こんなやつです。
Control.scala

object Control {
  type Closeable = { def close(): Unit }

  def using[A <: Closeable, B](resource: A)(body: A => B): B = {
    try {
      body(resource)
    } finally {
      if (resource != null) resource.close()
    }
  }
}

closeメソッドを持った何かにCloseableという別名を与え、それを引数としてcloseメソッドを持ったクラスのインスタンスであれば何でも渡せるようになっています。あと、引数宣言の箇所が()2つと見慣れない感じになっていますが、これが「関数を返す関数」の短縮表記ってところですか。

使いかたは、こう。
UsingTest.scala

object UsingTest {
  def main(args: Array[String]): Unit = {
    import Control._

    val result = using(new CloseableObject("Hello World")) { c =>
      println("inner using")
      c.decorate
    }

    println(result)
}

class CloseableObject(value: String) {
  println("new instance[%s]".format(value))

  def decorate: String = "*****[%s]*****".format(value)

  def close(): Unit = println("close instance")
}

closeメソッドを持ったCloseableObjectというクラスをnewしてusingに渡し、中で何か処理をしてusingの外へ値を戻しています。

これを実行すると、こうなります。

$ fsc Control.scala UsingTest.scala
$ scala UsingTest
new instance[Hello World]
inner using
close instance
*****[Hello World]*****

new → using内部 → closeの呼び出し、そしてusing内で評価した値がusingの結果としてresultへ渡っているのが確認できます。

ちなみに、このusingは重ねることができるので、よくあるJavaのファイル読み込み処理は、以下のように書き直せます。

    import scala.annotation.tailrec
    import java.io._

    @tailrec
    def lineMap(reader: BufferedReader, lines: List[String]): List[String] = reader readLine() match {
      case null => lines reverse
      case line => lineMap(reader, line :: lines)
    }

    val content =
      using(new FileInputStream("UsingTest.scala")) { fis =>
        using(new InputStreamReader(fis, "UTF-8")) { isr =>
          using(new BufferedReader(isr)) { reader =>
            lineMap(reader, Nil)
          }
        }
      }

    content foreach(println)

これで、ファイル「UsingTest.scala」の内容を全部読み取り、Listとしてusingの外へ返します。また、その時には各StreamおよびReaderは全部closeされます。

ところで、高階関数理解のためにカリー化の短縮表記で書かれたusingを、通常の「関数を返す関数」のベタな表記として書き直してみようかと思いまして。

まずは、内部関数を定義して返すバージョン。

  def using2[A <: Closeable, B](resource: A): (A => B) => B = {
    def using2Inner(body: A => B): B = {
      try {
        body(resource)
      } finally {
        if (resource != null) resource.close()
      }
    }

    using2Inner
  }

え〜と、resourceを引数を渡されて呼び出されると、「Aを引数に取ってBを返す関数」を引数に取りBを返す関数がusing2になります。んで、返されるのは内部で定義されたusing2Innderで、これがAを引数に取ってBを返す関数を引数に取ってBを返す関数ですね。ああ、ややこしい…。

これをさらに関数リテラルを使って書き直します。

  def using3[A <: Closeable, B](resource: A): (A => B) => B = {
    (body: A => B) => {
      try {
        body(resource)
      } finally {
        if (resource != null) resource.close()
      }
    }
  }

関数リテラルでは、戻り値の型の表記はしません。引数bodyの戻り値より型は推論されます。が、関数を引数に取る関数リテラルの表記に若干ハマりました…。

とりあえず、どちらのバージョンも元のusingと同じ動きをするので、OKかな?