Wednesday, September 25, 2019 / Haskell, JavaScript, functional-programming

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

入門 Haskell プログラミング」の Section 10 に Haskell で オブジェクト指向的なコード例が出ていた。 興味深い。ただ、Haskell のコードを見てもさっぱり意味がわからないので、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"

JavaScript で書き直す

上記の Haskell コードを JavaScript で書き直してみます。
しかし、 よくわからなかったのが cup ml = \messageFunctionn -> messageFunction ml とか \ml -> ml の部分です。

/ml -> ml は haskell の匿名関数の記述方法なので、JavaScript では以下のようにかけるはず.

function(ml){
    return ml;
}

同様に \messageFunction -> messageFunction ml は:

function(messageFunction){
    return messageFunction(ml);
}

さらに cup ml = \messageFunctionn -> messageFunction ml は:

var cup = function(ml){
    return function(messageFunction){
        return messageFunction(ml);
    };
};

ということでしょうか。

その他の部分は、単純にJavaScript に翻訳できそうです。

var cup = function(ml){
    return function(messageFunction){
        return messageFunction(ml);
    };
};
var getML = function(aCup){
    return aCup( function(ml){ return ml; } );
};
var drinkOneSip = function(aCup){
    var newValue = getML(aCup) - 10;
    return cup(newValue);
};

var showML = function(aCup){
    return getML(aCup) + ' ml';
}

var coffeeCup1 = cup(100); // コーヒーカップにコーヒーを100ml用意
console.log( showML( coffeeCup1 ) );

var coffeeCup2 = drinkOneSip(coffeeCup1); // 状態1 のコーヒーカップから一口飲む
console.log( showML( coffeeCup2 ) );

var coffeeCup3 = drinkOneSip(coffeeCup2); // 状態2 のコーヒーカップから一口飲む
console.log( showML( coffeeCup3 ) );

果たして実行してみると、

$ node main.js
100 ml
90 ml
80 ml

意図通りに翻訳できたようです。

オブジェクト指向脳で普通に JavaScript 記述したらどうなるのか?

関数型で実装するとこうなる、ということですが、普通のオブジェクト指向の発想で実装してみます。

class Cup {
    constructor(ml){
        this.ml = ml;
    }

    getML(){
        return this.ml;
    }

    drinkOneSip(){
        this.ml = this.ml -10;
    }
}

const coffeeCup = new Cup(100);
console.log(coffeeCup.getML());

coffeeCup.drinkOneSip();
console.log(coffeeCup.getML());

coffeeCup.drinkOneSip();
console.log(coffeeCup.getML());

もう少し、元の関数型発想のコードに近づけてみると:

class Cup {
    constructor(ml){
        this.ml = ml;
    }

    static getML(aCup){
        return aCup.ml;
    }

    static drinkOneSip(aCup){
        var newValue = Cup.getML(aCup) -10;
        return new Cup(newValue);
    }
}

const coffeeCup1 = new Cup(100);
console.log(Cup.getML(coffeeCup1));

const coffeeCup2 = Cup.drinkOneSip(coffeeCup1);
console.log(Cup.getML(coffeeCup2));

const coffeeCup3 = Cup.drinkOneSip(coffeeCup2);
console.log(Cup.getML(coffeeCup3));

こんな感じでしょうか。

まとめ

変数を持たないで、変数的なものを表現する(ここでは コーヒーカップに何ミリリットル、コーヒーが入っているかの値)という部分がまだ釈然としない。

var cup = function(ml){
  return function(messageFunction){
     return messageFunction(ml);
  };
};

この部分で var aCup = cup(100) とした時点で aCup に 100 という状態が保持できている・・・という。

messageFunctionmlGetterFunction に改名すれば、もう少しわかったような気になりそう。

var cup = function(ml){
  return function(mlGetterFunction){
     return mlGetterFunction(ml);
  };
};

cup(100) のようにしたときに返されるのは function(mlGetterFunction){ return mlGetterFunction(100) } なので、 ここに保持されている 100 という値を取り出すには、var mlGetterFunction = function(ml){ return ml; } を適用すればよい、ということになるのか。