C++ で Maybeモナドを書いてみた
C++ で Maybeモナドを書いてみたら意外と面白かったので覚書。
まぁこの手のネタはたくさんあると思いますが。
あんまり Haskell 力は高くないのでざっくりとした実装です。
[maybe と return]
maybe 自体は同等の機能を持つ Boost.Optional で代用。
nothing も boost::none ですね。
template<typename T> using maybe = boost::optional<T>; constexpr auto const& nothing = boost::none; template<typename T> maybe<T> return_(T t){ return t; } maybe<int> division(int a, int b){ return b ? return_(a / b) : nothing; } const auto const result = division(4, 2); assert(result); assert(*result == 2); const auto result2 = division(4, 0); assert(!result2);
Template Aliases だと分かりやすく名前付け出来てイイネ!
just は今回は省略。
[bind]
maybe と関数を受け取って just (真)なら関数を評価して返します。
今回は関数テンプレートの場合は引数を推論したかったので2種類に分けました。
基本的には上の bind 関数のみで大丈夫かな。
template<typename T, typename F> auto bind(maybe<T> m, F f) ->decltype(f(*m)){ return m ? f(*m) : nothing; } // (>>=) template<typename T, typename F> auto operator >>=(maybe<T> m, F f) ->decltype(bind(m, f)){ return bind(m, f); } template<typename T> maybe<T> bind(maybe<T> m, maybe<T>(*f)(T)){ return m ? f(*m) : nothing; } // (>>=) template<typename T> maybe<T> operator >>=(maybe<T> m, maybe<T>(*f)(T)){ return bind(m, f); } template<typename T> T twice(T x){ return x + x; } const auto result = bind(division(12, 2), [](int n){ return division(n, 3); }); assert(result); assert(*result == 2); // twice は戻り値型が maybe<T> ではないので const auto result2 = bind(division(4, 0), [](int n){ return return_(twice(n)); }); assert(!result2);
受け取る関数は戻り値型が maybe
ここら辺は Haskell だと関数合成(.)が使えるので楽なんですけどねぇ。
あと Haskell っぽく operator >>= を定義していますが、C++ の場合 >>= は右結合なので括弧が必要になってしまって悲しい…。
[ソース]
#include <boost/optional.hpp> template<typename T> using maybe = boost::optional<T>; constexpr auto const& nothing = boost::none; template<typename T> maybe<T> return_(T t){ return t; } template<typename T, typename F> auto bind(maybe<T> m, F f) ->decltype(f(*m)){ return m ? f(*m) : nothing; } // (>>=) template<typename T, typename F> auto operator >>=(maybe<T> m, F f) ->decltype(bind(m, f)){ return bind(m, f); } // 関数テンプレートを受け取る版 // 戻り値型が maybe<T> の時のみ受け取れる template<typename T> maybe<T> bind(maybe<T> m, maybe<T>(*f)(T)){ return m ? f(*m) : nothing; } // (>>=) template<typename T> maybe<T> operator >>=(maybe<T> m, maybe<T>(*f)(T)){ return bind(m, f); } template<typename T> maybe<T> division(T a, T b){ return b ? return_(a / b) : nothing; } template<typename T> maybe<T> sqrt(T x){ if( x < 0 ){ return nothing; } int i; for(i = 0; i * i <= x ; ++i){} return i-1; } #include <iostream> template<typename T> void print(maybe<T> m){ if( m ){ std::cout << *m << std::endl; } else{ std::cout << "Nothing" << std::endl; } } template<typename T> T twice(T x){ return x + x; } int main(){ auto result = division(12, 3); print(result); // => 4 auto result2 = division(2, 0); print(result2); // => Nothing // 生で処理 if( result ){ auto result2 = sqrt(*result); print(result2); // => 2 } // bind 版 print(bind(result, sqrt)); // => 2 print(result >>= sqrt); // => 2 auto div12 = [](int n){ return division(12, n); }; auto r_twice = [](int n){ return return_(twice(n)); }; bind(maybe<int>(10), div12); print(bind(bind(result, sqrt), div12)); // => 6 print(( result >>= sqrt) >>= div12); // => 6 print(((result >>= sqrt) >>= div12) >>= r_twice); // => 12 print(((result >>= sqrt) >>= [](int n){ return division(n, 0); }) >>= r_twice); // => Nothing print((((return_(1) >>= r_twice) >>= r_twice) >>= r_twice) >>= r_twice); // => 16 return 0; }
[出力]
4 Nothing 2 2 2 6 6 12 Nothing 16
実際に使ってみるとこんな感じになります。
最初は何が嬉しいのか分からなかったんですが、bind を使用することで立て続けに処理することが出来るのがいいですね。
真偽は最後に確認するだけでいいので、だいぶコードがすっきりとします。
次は lookup 関数とかつくってみたいところ。
他のモナドも触ってみたいなー。