C++ で Maybeモナドを書いてみた

C++ で Maybeモナドを書いてみたら意外と面白かったので覚書。
まぁこの手のネタはたくさんあると思いますが。
あんまり Haskell 力は高くないのでざっくりとした実装です。

[実装する主な機能]

  • maybe
    • nothing
  • return
  • bind(>>=)


今回はここら辺の機能を C++ で実装してみました。

[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 でなければいけないので、戻り値型が maybe でない関数の場合は return_ でラップする必要があります。
ここら辺は 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 関数とかつくってみたいところ。
他のモナドも触ってみたいなー。