関数型オブジェクト指向プロラミングを JavaScript に翻訳する というエントリーを2年前に書いた。 Haskell のオブジェクト指向的なコードを JavaScript に書き直したエントリーだが、今読み返してもいまいちピンとこない。 ならば Golang で書き直してみよう。 Golang であれば、JavaScript と違って型を明示的にコード中にかけるので、わかりやすくなるのではないか?
便宜上、コーヒーカップには 100ml のコーヒーが入り、一口飲むと 10ml 消費されることにします.
cup ml = \messageFunction -> messageFunction ml
getML aCup = aCup (\ml -> ml)
drinkOneSip aCup = cup newValue
where newValue = getML aCup -10 -- 10ml 消費する
showML aCup = show (getML aCup) ++ " ml"
main :: IO ()
main = do
let coffeeCup1 = cup 100 -- コーヒーカップにコーヒーを100ml用意
print (showML coffeeCup1)
let coffeeCup2 = drinkOneSip coffeeCup1 -- 状態1 のコーヒーカップから一口飲む → 状態2 に変化したコーヒーカップを返す.
print (showML coffeeCup2)
let coffeeCup3 = drinkOneSip coffeeCup2 -- 状態2 のコーヒーカップから一口飲む → 状態3 に変化したコーヒーカップを返す.
print (showML coffeeCup3)
これを main.hs に保存して ghc main.hs すると main が生成される. 実行結果は以下の通り:
./main
"100 ml"
"90 ml"
"80 ml"
上記の Haskell コードを Golang で書き直してみます。
main.go:
package main
import (
"fmt"
)
type MessageGetter func(int) int
type MessageKeeper func(MessageGetter) int
func cup(ml int) MessageKeeper {
return func(mg MessageGetter) int {
return mg(ml)
}
}
func getML(aCup MessageKeeper) int {
return aCup(func(ml int) int { return ml })
}
func drinkOneSip(aCup MessageKeeper) MessageKeeper {
newValue := getML(aCup) - 10
return cup(newValue)
}
func showML(aCup MessageKeeper) string {
return fmt.Sprintf("%v ml", getML(aCup))
}
func main() {
// コーヒーカップの状態その1:
coffeeCup1 := cup(100)
fmt.Println(showML(coffeeCup1))
// コーヒーカップの状態その2:
coffeeCup2 := drinkOneSip(coffeeCup1)
fmt.Println(showML(coffeeCup2))
// コーヒーカップの状態その2:
coffeeCup3 := drinkOneSip(coffeeCup2)
fmt.Println(showML(coffeeCup3))
}
実行してみます:
$ go run main.go
100 ml
90 ml
80 ml
うむ、Golang では 型を明示的に定義できるので、格段にわかりやすくなった。
さらに、より意味が通るように型名/関数名を修正してみます。
package main
import (
"fmt"
)
type CoffeeGetter func(int) int
type CoffeeKeeper func(CoffeeGetter) int
func createCoffeeCup(ml int) CoffeeKeeper {
return func(coffeeGetter CoffeeGetter) int {
return coffeeGetter(ml)
}
}
func getML(aCup CoffeeKeeper) int {
coffeeGetter := func(ml int) int { return ml }
return aCup( coffeeGetter )
}
func drinkOneSip(aCup CoffeeKeeper) CoffeeKeeper {
newValue := getML(aCup) - 10
return createCoffeeCup(newValue)
}
func showML(aCup CoffeeKeeper) string {
return fmt.Sprintf("%v ml", getML(aCup))
}
func main() {
// コーヒーカップの状態その1:
coffeeCup1 := createCoffeeCup(100) // 100ml 入りのコーヒーカップをつくる.
fmt.Println(showML(coffeeCup1)) // 当然 100ml と出力される.
// コーヒーカップの状態その2:
coffeeCup2 := drinkOneSip(coffeeCup1) // 100ml 入りのコーヒーカップから 一口(10ml) 飲む.
fmt.Println(showML(coffeeCup2)) // 90ml と出力される.
// コーヒーカップの状態その2:
coffeeCup3 := drinkOneSip(coffeeCup2) // 90ml 入りのコーヒーカップから 一口(10ml) 飲む.
fmt.Println(showML(coffeeCup3)) // 80ml と出力される.
}
func createCoffeeCup(ml int) CoffeeKeeper {
return func(coffeeGetter CoffeeGetter) int {
return coffeeGetter(ml)
}
}
コーヒーカップを生成するときに、return されるのは CoffeeKeeper 型。 CoffeeKeeper 型は CoffeeGetter型を受け取って int (コーヒーの内容量ml) を返す関数。
その CoffeeKeeper の実装はこれ:
func(coffeeGetter CoffeeGetter) int { return coffeeGetter(ml) }
createCoffeeCup したときに、この関数が return される。
もし、createCoffeeCup(100) とすれば、return される関数はこれ:
func(coffeeGetter CoffeeGetter) int { return coffeeGetter(100) }
結局この匿名関数の内側に 指定されたコーヒー容量 100ml が保持されるというトリックになっている。(わかり辛い!)
元のコードは、匿名関数で コーヒー(ml)を取得していた部分:
func getML(aCup MessageKeeper) int {
return aCup(func(ml int) int { return ml })
}
これ func(ml int) int { return ml }
が匿名関数のままだったのでわかり辛かったのだが、
これに coffeeGetter という名前(そしてもちろんこれは CoffeeGetter タイプなのだが)をつけることで
意味がわかるようになった。
func getML(aCup CoffeeKeeper) int {
coffeeGetter := func(ml int) int { return ml }
return aCup( coffeeGetter )
}
コーヒーキーパーである コーヒーカップ(aCup) に coffeeGetter 関数を適用することで、内容物であるコーヒー量(ml)を得ることができる、と。
これなら意味が理解できた気がする。