CLOVER🍀

That was when it all began.

Groovy、Scala、Clojureで関数合成

あまり使ったことがないので、メモを兼ねて試してみます。

Groovy

Groovyの場合は、Closureクラスのインスタンスを合成します。使用するメソッドは、leftShift(<<)とrightShift(>>)です。

def add2 = { x -> x + 2 }
def multiply3 = { x -> x * 3 }

def multiply3AndAdd2 = add2 << multiply3  // f << g は f(g(x))
def add2AndMultiply3 = add2 >> multiply3  // f >> g は g(f(x))

println(multiply3AndAdd2(2))  // (3 * 2) + 2 => 8
println(add2AndMultiply3(2))  // (2 + 2) * 3 => 12
f << g

でf(g(x))なので、gの結果をfに適用するよ!

f >> g

でg(f(x))なので、fの結果をgに適用するよ!と覚えればいいかな?

合成できるのはあくまでClosureクラスのインスタンスなので、メソッドを合成したい場合は1度Closureクラスに変換してあげる必要があります。

def add2Method(x) {
    x + 2
}

def multiply3Method(x) {
    x * 3
}

def multiply3AndAdd2ByMethod = this.&add2Method << this.&multiply3Method
def add2AndMultiply3ByMethod = this.&add2Method >> this.&multiply3Method

println(multiply3AndAdd2ByMethod(2)) // => 8
println(add2AndMultiply3ByMethod(2)) // => 12

「.&」演算子でメソッドをClosureに変換できるそうなのですが、単独で使ってもダメで必ずレシーバーが必要なようです…。また、「&」演算子なわけでもありません。つまり、このような記法や

def multiply3AndAdd2ByMethod = .&add2Method << .&multiply3Method
def add2AndMultiply3ByMethod = .&add2Method >> .&multiply3Method

このような記法は、共にNGです。

def multiply3AndAdd2ByMethod = &add2Method << &multiply3Method
def add2AndMultiply3ByMethod = &add2Method >> &multiply3Method

トップレベルのメソッドに使用したい場合は、thisを付けてってことですね。

Scala

Scalaの場合は、Function1トレイトのインスタンスを合成します。

val add2 = (x: Int) => x + 2
val multiply3 = (x: Int) => x * 3

val multiply3AndAdd2 = add2 compose multiply3  // f compose g は f(g(x))
val add2AndMultiply3 = add2 andThen multiply3  // f andThen g は g(f(x))

println(multiply3AndAdd2(2))  // (3 * 2) + 2 => 8
println(add2AndMultiply3(2))  // (2 + 2) * 3 => 12

andThenがあるので、逆がcomposeと最初は見ればいい…?ま、慣れれば迷わなくなりますね。

メソッドを合成する場合は、1度Function1トレイトのインスタンスに変換してあげる必要があります。

def add2Method(x: Int): Int = x + 2
def multiply3Method(x: Int): Int = x * 3

val multiply3AndAdd2ByMethod = add2Method _ compose multiply3Method
val add2AndMultiply3ByMethod = add2Method _ andThen multiply3Method

println(multiply3AndAdd2ByMethod(2))  // => 8
println(add2AndMultiply3ByMethod(2))  // => 12

「_」を付けて、部分適用せい、と。

Clojure

Clojureは、そのまま関数定義を合成できます。

(defn add2 [x]
  (+ x 2))

(defn multiply3 [x]
  (* x 3))

(def multiply3AndAdd2 (comp add2 multiply3))  ;; comp f g は (f (g x))

(println (multiply3AndAdd2 2))  ;; (+ (* 2 3) 2) => 8

もちろん、無名関数から合成しても問題ありません。

(def f #(+ % 2))
(def g #(* % 3))

(println ((comp f g) 2))  ;; => 8

まあ、defnってマクロらしいから、意味一緒なのかな…。

ただ、g(f(x))となるような関数合成を行う関数は、標準にはないみたいですね。