読者です 読者をやめる 読者になる 読者になる

算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

Kotlinで関数の合成と部分適用

Haskellでは関数の部分適用とか関数の合成ができますが、その仕組みをKotlinでも再現しようという実験、という名の遊びです。

案外うまくキレイに出来ました。

関数の部分適用

次のような拡張関数を用意して、実現しました。

fun <X, Y, R> Function2<X, Y, R>.invoke(x : X) = {
  (y : Y) -> this(x, y)
}

これがあれば、関数オブジェクトを部分適用することが出来ます。

fun main(args : Array<String>) {
  val plus = {
    (a : Int, b : Int) -> a + b
  }
  
  println(plus(2, 3)) // 5
  println(plus(2)(3)) // 5
  println(listOf(1, 2, 3).map(plus(5))) // [6, 7, 8]
}

解説

Kotlinでは、関数オブジェクトはあるクラスのインスタンスとして存在します。 引数なしの関数オブジェクトはFunction0クラスのインスタンス、引数1つの関数オブジェクトはFunction1クラスのインスタンス...という具合に。 なので、今回は2引数関数を対象としたため、Function2クラスの拡張関数を定義しました。

さらにKotlinでは、invokeという名前のメソッドはメソッド名を省いて呼び出すことができます。 つまりfuncという変数に対して、invokeメソッドを呼び出すにはfunc.invoke()と書くこともできますし、単にfunc()と記述することもできます。

したがって、Function2クラスの拡張関数としてinvokeを定義することによって2引数関数オブジェクトの部分適用を実現できました。

関数の合成

関数の合成は、Function1クラスの拡張関数として演算子オーバロードを利用して定義しました。

fun <X, Y, Z> Function1<X, Y>.plus(f : (Z) -> X) = {
  (z : Z) -> this(f(z)) 
}

Xを取ってYを返す関数 + Zを取ってXを返す関数、と記述すると、Zを取ってYを返す関数が生成されます。

fun main(args : Array<String>) {
  // 文字列を空白文字で区切って配列化する
  val words = {
    (str : String) -> str.split("""\s""")
  }
  
  // 配列の要素数を返す
  val size = {
    (array : Array<*>) -> array.size
  }
  
  // 文字列の単語数を返す : 関数合成によって生成
  val countWords = size + words
  
  println(countWords("I love Kotlin")) // 3
}

感想

Kotlin面白い!!

関数の合成やら部分適用やらは非常に便利ですが、Kotlinにこの機能はいらないかな(設計者もそう思ったんでしょう)。 Kotlinは関数型言語じゃないし。