「入門 Haskell プログラミング」の Section 10 に Haskell で オブジェクト指向的なコード例が出ていた。 興味深い。ただ、Haskell のコードを見てもさっぱり意味がわからないので、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 コードを JavaScript で書き直してみます。
しかし、
よくわからなかったのが cup ml = \messageFunction -> messageFunction ml
とか \ml -> ml
の部分です。
\ml -> ml
は haskell の匿名関数の記述方法なので、JavaScript では以下のようにかけるはず.
function(ml){
return ml;
}
同様に \messageFunction -> messageFunction ml
は:
function(messageFunction){
return messageFunction(ml);
}
さらに cup ml = \messageFunction -> 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
意図通りに翻訳できたようです。
関数型で実装するとこうなる、ということですが、普通のオブジェクト指向の発想で実装してみます。
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 という状態が保持できている・・・という。
messageFunction を mlGetterFunction に改名すれば、もう少しわかったような気になりそう。
var cup = function(ml){
return function(mlGetterFunction){
return mlGetterFunction(ml);
};
};
cup(100)
のようにしたときに返されるのは function(mlGetterFunction){ return mlGetterFunction(100) }
なので、
ここに保持されている 100 という値を取り出すには、var mlGetterFunction = function(ml){ return ml; }
を適用すればよい、ということになるのか。