1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavascriptでEitherモナドを真似してみた。

Last updated at Posted at 2024-02-10

通勤時に川沿いの遊歩道を歩きながら随伴と責圏(直積)の事を何気なくイメージしていたら突然モナドとの繋がりが頭に浮かんで来ました。そこで早速 Javascript で模倣すべくコードを書いてみる事にしました。モナドの動きが理解しやすい様に配列と短い関数の積み重ねで模倣して見ました。前回の State モナドよりも単純で追いかけ易いコードで記述するように試みてみました。
image.png

javascript Either.js
//
// Haskell の Either Monad を模倣してみた。
//

// 型再定義 (type)
var _right_    = true;
var _left_     = false;

var Right      = val => { return [val, _right_];         };
var Left       = msg => { return [msg, _left_ ];         };
var fst        = eth => { return eth[0];                 };
var snd        = eth => { return eth[1];                 };
var isRight    = eth => { return (snd(eth) === _right_); };
var isLeft     = eth => { return (snd(eth) === _left_ ); };
var showEither = eth => { if (isRight(eth))
                          return "Right " + fst(eth);
                          return "Left  " + fst(eth);    };
// 戻り値なし => アクション
var prt        = eth => { console.log(showEither(eth));  };

// eBind は Haskell の Either Monad 空間の >>= (bind) を模倣
// Either を受け取り一般引数と併せて指定された関数に適用する
// その結果を Right か Left の値で戻す。
var eBind  = (f,a,b) => { if (isLeft(a))
                          return a;
                          return f(fst(a), b);           };

var add = (a, b) => { return Right(a + b); };
var sub = (a, b) => { return Right(a - b); };
var mul = (a, b) => { return Right(a * b); };
var div = (a, b) => { if (b === 0)
                      return Left ("Division by Zero");
                      return Right(a / b); };

prt(eBind(add, Left ("Null")           , 5));  // Left  Null
prt(eBind(add, Right(10)               , 5));  // Right 15
prt(eBind(div, Right(10)               , 5));  // Right 2
prt(eBind(div, Right(10)               , 0));  // Left  Division by Zero
prt(eBind(mul, eBind(div, Right(10), 5), 5));  // Right 10
prt(eBind(mul, eBind(div, Right(10), 0), 5));  // Left  Division by Zero

Javascript と同じ処理を Haskell でも書いてみましたので比較して見て下さい。

Haskell Either.hs
--
-- Javascript Either Monad と同じ処理を Haskell で書いてみた。
--

add_ :: Float -> Float -> Either String Float
add_ = \a b -> Right (a + b)

sub_ :: Float -> Float -> Either String Float
sub_ = \a b -> Right (a - b)

mul_ :: Float -> Float -> Either String Float
mul_ = \a b -> Right (a * b)

div_ :: Float -> Float -> Either String Float
div_ = \a b -> if b == 0
               then Left "Division by Zero"
               else Right (a / b)

Left "Null"               >>= add_ 5 -- Left "Null"
Right 10                  >>= add_ 5 -- Right 15.0

Right 10 >>= flip div_  5            -- Right 2.0
Right 10 >>= flip div_  0            -- Left "Division by Zero"

Right 10 >>= flip div_  5 >>= mul_ 5 -- Right 10.0
Right 10 >>= flip div_  0 >>= mul_ 5 -- Left "Division by Zero"

10               `div_` 5 >>= mul_ 5 -- Right 10.0
10               `div_` 0 >>= mul_ 5 -- Left "Division by Zero"

-- 減算(sub_)と除算(div_)の引数aとbは順序を入れ替えられない。

-- Right b >>= sub_ a
-- Right b >>= div_ a

-- と認識するためflip関数で引数の順序を入れ替えて

-- Right b >>= flip sub_ a  =>  sub_ b a
-- Right b >>= flip dib_ a  =>  div_ b a 

-- として演算する。

Javascript の書き方は一般的に知られているお作法とは異なりますが、見た目の簡潔さを重視しました。型のチェックは人力でお願いします。今回は Either を題材にモナドがどの様にプログラミングで使われているかを示すため、超絶分かり易いコードで模倣してみました。プログラミングを始めたばかりの方にも読める短いコードで書かれた関数を組み合わせて実現していますので、内部の動きが掴みやすくなっていると思います。
Javascript のコードを書いた後、コード・フェイスがメソッド・チェーンの様にできなかったので、他のコードとの組み合わせが難しそうに感じます。それでも今回の「モナドってこんな感じ?」を掴もうという試みはなんとか達成しているのではないかと思っています。

/* ========= 余談追加分 ============ */

下書き中にメソッド・チェーンの事を言及したら、突如リスト系操作関数も作ってみようと思い立ってしまい。余談ながら追記することにしました。

Javascript List.js
//
// Haskell の List 関連関数を模倣してみた。
//

var head    = lst => { return lst[0];                     };
var tail    = lst => { return lst.slice(1);               };
var init    = lst => { return lst.slice(0, lst.length-1); };
var last    = lst => { return lst.slice(   lst.length-1); };
var take = (n,lst)=> { return lst.slice(0, n           ); };
var drop = (n,lst)=> { return lst.slice(n,             ); };
var lget    = lst => { return [head(lst), tail(lst)];     };
var isEmpty = lst => { return (lst.length === 0);         };

var map    = (fnc, lst) => {
      if     (isEmpty(lst)) return [];
      var    [x, xs] = lget(lst);
      return [fnc(x)].concat(map(fnc, xs));
    }

var filter = (fnc, lst) => {
      if     (isEmpty(lst)) return [];
      var    [x, xs] = lget(lst);
      return (fnc(x) ? [x] : []).concat(filter(fnc, xs));
    }

var foldl  = (fnc, acc, lst) => {
      if     (isEmpty(lst)) return acc;
      var    [x, xs] = lget(lst);
      return foldl(fnc, fnc(acc, x), xs);
    }
    
// 実行結果
foldl((a,x) => { return a + x }
    , 0
    , [1,2,3,4,5,6,7,8,9,10]);
// 55

foldl((a,x) => { return a.concat(x + 1) }
    , []
    , [1,2,3,4,5]);
// [2,3,4,5,6]

foldl(      (a,x) => { return a + x }, 0
   , (map   (x    => { return x * 2 }
   , (filter(x    => { return x < 6 }
   , (take  (3
   , (drop  (4, [0,1,2,3,4,5,6,7,8,9])))))))));
// 10

と一応作っては見ましたが、引数を括弧で括るのが意外と鬱陶しく感じました。Haskell の様に「 . 」で関数合成できると見た目がスッキリするのと閉じ括弧の数を気にしなくても良いという利便性に気が付きました。

※因みに head と last に空リストを渡すと undefined を返しますがご愛敬という事でご勘弁願います。

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?