C++でMaybeモナドを返すlookup関数を作ってみた

ネタ元:C++でMaybeモナド


shand/maybe.hpp

#ifndef SHAND_MAYBE_INCLUDE
#define SHAND_MAYBE_INCLUDE

#include <boost/variant.hpp>
#include <boost/utility/result_of.hpp>
#include <boost/bind.hpp>
#include <algorithm>
#include <utility>
#include <vector>
#include <ostream>

namespace shand {

class nothing {};

template <class T>
class just {
public:
    T val;
    just(const T& val) : val_(val) {}
};

template <class T>
struct maybe {
    typedef boost::variant<nothing, just<T> > type;
};

template <class T>
inline typename maybe<T>::type monad_return(const T& x)
{
    return just<T>(x);
}

template <class T, class First, class Second>
inline typename maybe<Second>::type lookup(const T& a, const std::vector<std::pair<First, Second> >& v)
{
    typedef std::vector<std::pair<First, Second> >::const_iterator iterator;

    iterator it = std::find_if(v.begin(), v.end(),
                            boost::bind(&std::pair<First, Second>::first, _1) == a);

    if (it == v.end()) {
        return nothing();
    }
    else {
        return just<Second>(it->second);
    }
}

template <class T>
class lookup_functor {
    T a_;
public:
    lookup_functor(const T& a)
        : a_(a) {}
    
    template <class Signature>
    struct argument_of;

    template <class R, class Arg>
    struct argument_of<R(Arg)> {
        typedef Arg type;
    };
    
    template <class Signature>
    struct second_type;
    
    template <class First, class Second>
    struct second_type<std::vector<std::pair<First, Second> > > {
        typedef Second type;
    };
    
    template <class Signature>
    struct result {
        typedef typename maybe<typename second_type<typename argument_of<Signature>::type>::type>::type type;
    };
    
    template <class First, class Second>
    typename maybe<Second>::type operator()(const std::vector<std::pair<First, Second> >& v) const
    {
        return lookup(a_, v);
    }
};

template <class T>
inline lookup_functor<T> lookup(const T& a)
{
    return lookup_functor<T>(a);
}

template <class T, class F>
inline typename boost::result_of<F(T)>::type
    operator>>=(const boost::variant<nothing, just<T> >& x, F f)
{
    if (boost::get<nothing>(&x) == 0) {
        return f(boost::get<just<T> >(x).val);
    }
    else {
        return nothing();
    }
}

template <class Elem, class Traits, class T>
inline std::basic_ostream<Elem, Traits>&
    operator<<(std::basic_ostream<Elem, Traits>& os, const boost::variant<nothing, just<T> >& x)
{
    if (const just<T> *p = boost::get<just<T> >(&x)) {
        os << p->val;
    }
    return os;
}

} // namespace shand

#endif // SHAND_MAYBE_INCLUDE
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <shand/maybe.hpp>

using namespace std;
using namespace shand;

vector<pair<string, string> > make(const std::pair<string, string>& p1)
{
    vector<pair<string, string> > tmp;
    tmp.push_back(p1);
    return tmp;
}

vector<pair<string, string> > make(const std::pair<string, string>& p1, const std::pair<string, string>& p2)
{
    vector<pair<string, string> > tmp;
    tmp.reserve(2);
    tmp.push_back(p1);
    tmp.push_back(p2);
    return tmp;
}

vector<pair<string, vector<pair<string, string> > > > config()
{
    vector<pair<string, vector<pair<string, string> > > > v;
	
    v.reserve(3);
    v.push_back(make_pair("database",  make(make_pair("path", "var/app/db"), make_pair("encoding", "euc-jp"))));
    v.push_back(make_pair("urlmapper", make(make_pair("cgiurl", "/app"),     make_pair("rewrite", "True"))));
    v.push_back(make_pair("template",  make(make_pair("path", "/var/app/template"))));
    return v;
}

int main()
{
    cout << ((monad_return(config()) >>= lookup(string("database"))) >>= lookup(string("encoding"))) << endl;
}
euc-jp


C++0xだとこう

#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <shand/maybe.hpp>

using namespace std;
using namespace shand;

int main()
{
    vector<pair<string, vector<pair<string, string>>>> config = {
        {"database",  {{"path", "var/app/db"}, {"encoding", "euc-jp"}}},
        {"urlmapper", {{"cgiurl", "/app"},     {"rewrite", "True"}}},
        {"template",  {{"path", "/var/app/template"}}}
    };
    
    cout << ((monad_return(config) >>= lookup(string("database"))) >>= lookup(string("encoding"))) << endl;
}


ちなみに、Haskellだとこう

main = print $ return config >>= lookup "database" >>= lookup "encoding"

config :: [(String, [(String, String)])];
config =
  [ ("database",  [("path", "var/app/db"), ("encoding", "euc-jp")]),
    ("urlmapper", [("cgiurl", "/app"), ("rewrite", "True")]),
    ("template",  [("path", "/var/app/template")]) ]


おまけ

#include <iostream>
#include <shand/maybe.hpp>

using namespace std;
using namespace shand;

maybe<int>::type inc(int x)
{
    if (x >= numeric_limits<int>::max()) {
        return nothing();
    }
    return monad_return(x+1);
}

int main()
{
    int start = numeric_limits<int>::max() - 2;
    cout << (monad_return(start) >>= inc)                               << endl;
    cout << ((monad_return(start) >>= inc) >>= inc)                     << endl;
    cout << (((monad_return(start) >>= inc) >>= inc) >>= inc)           << endl; // nothingなので何も表示されない
    cout << ((((monad_return(start) >>= inc) >>= inc) >>= inc) >>= inc) << endl; // 最後のincは呼ばれない
}


Maybeモナドを表現するのにboost::variant >を使ってるけど、
HaskellのNothingとJustにこだわらないならboost::optionalを使ったほうがいいと思うんだ。


Initializer listsとCallableコンセプトとラムダ式とTemplate Aliasesがあればもっと簡単に書けるんだけど・・・
早くC++0xを使いたいな。