Home About
Haskell , Golang , Functional Programming

関数型オブジェクト指向プロラミングを Golang に翻訳する

関数型オブジェクト指向プロラミングを JavaScript に翻訳する というエントリーを2年前に書いた。 Haskell のオブジェクト指向的なコードを JavaScript に書き直したエントリーだが、今読み返してもいまいちピンとこない。 ならば Golang で書き直してみよう。 Golang であれば、JavaScript と違って型を明示的にコード中にかけるので、わかりやすくなるのではないか?

Haskell でコーヒーカップに入っているコーヒーを飲む を記述(再掲載)

便宜上、コーヒーカップには 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"

Golang で書き直す

上記の 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 を取得する部分

元のコードは、匿名関数で コーヒー(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)を得ることができる、と。

これなら意味が理解できた気がする。