(2009.1.3)
C++ で Haskell の Maybe
モナドっぽいものを書いてみます。意味が近い boost::optional
を活用してみます。
(2020.5) C++17 std::optional
で作りなおしました。ついでに, Either
っぽいのも作ってみました。
HaskellのMaybe
モナド
HaskellのMaybe
モナド (データ型) は, 正常値か失敗のどちらかを持つ型です。正常値は Just 値
, 失敗は Nothing
で表します。
>>=
演算子で Maybe
モナドを返す関数を繋いでいき、途中で失敗したら以降の関数は実行させないようになっています。>>=
演算子は, bind と呼ばれます。
モナド (型クラス) とは何か、満たすべき制約などについては、別ページにて; Haskell: モナド, 具象データ型, モナド則の嬉しさ
典型的な使い方は、実行時 (コンパイル時ではなく) の値の探索です。
組み込みの lookup
関数を使ってみます。lookup
関数は、引数を2つ取り, 第1引数が探す key, 第2引数が探す対象のリストです。見つかった場合はタプルの2番目を返し, 見つからなかったときは Nothing
を返します。こういう定義です;
Haskell
- lookup :: (Eq a) => a -> [(a,b)] -> Maybe b
- lookup key [] = Nothing
- lookup key ((x,y):xys)
- | key == x = Just y
- | otherwise = lookup key xys
例えば、次の例で tmpdir
関数を定義していますが、まず "path" で探し、見つかったらさらに "tmp" を探す、というのを愚直に実装しています。
Haskell
- config :: [(String, [(String, String)])]
- config = [
- ("path", [("tmp", "/tmp"), ("data", "/foo")]),
- ("user", [("u1", "hoge")])
- ]
-
- tmpdir :: Maybe String
- tmpdir = case (lookup "path" config) of
- Just entries -> lookup "tmp" entries
- Nothing -> Nothing
-
- main = print $ tmpdir
この関数 tmpdir
を次のように書けます。
Haskell
- tmpdir = lookup "path" config >>= lookup "tmp"
Maybe
は次のように実装されています。>>=
の左辺の値が Nothing
のときは, 右側の関数を評価 (実行) せず、Nothing
を返すようになっています。
Haskell
- instance Monad Maybe where
- (Just x) >>= k = k x
- Nothing >>= _ = Nothing
- (>>) = (*>)
- return = Just
- fail _ = Nothing
C++の optional
(2020.5) C++17 で std::optional
が導入されました。今後はこちらを使えます。
boost のoptional
クラスはHaskellのMaybeモナドのような機能を提供します。
boostがインストールされていれば、<boost/optional.hpp>ヘッダを取り込むだけで使えます。
値が有効なときは boost::optional<
値の型>(
値)
で値を持つオブジェクトを生成し、無効の場合は boost::optional<
値の型>()
で値を持たない optional
オブジェクトを生成します。
optional
には operator bool
が定義されており、if文などで値が有効かどうか判定できます。
また、operator *と-> が定義されており、これらの演算子で値を取り出せます。
C++
- #include <cmath>
- #include <iostream>
- #include <boost/optional.hpp>
- using namespace std;
-
-
- boost::optional<double> my_sqrt(double x) {
- if (x < 0)
- return boost::optional<double>();
- else
- return boost::optional<double>(sqrt(x));
- }
-
- int main() {
- boost::optional<double> y = my_sqrt(150.0);
- if (y)
- cout << *y << endl;
- else
- cout << "error.\n";
- return 0;
- }
optional
に渡す型は、デフォルトコンストラクタはなくても構いませんが、(暗黙にでも) コピーコンストラクタを持たなければなりません。
C++でMaybe
モナドっぽく
C++17 前提。
Haskell >>=
(bind) 演算子とlookup
を書いてみます。
C/C++ の >>=
演算子は右結合なので、そのままではまずい。>=
にします。この >=
は汎用的なモナドではありません。std::optional<>
固定です。
lookup()
は, std::map
あるいは tr1::unordered_map
を受け付けるようにしてみました。キーも自由に選べます。戻り値はoptional
型です。
Haskellでは2引数の関数に一つだけ実引数を与えると1引数の関数が得られます (関数の部分適用)。C++では bind()
を使います。
C++ は型推論が弱いので, 型をいたるところで書かないといけなくて大変です。
C++
- #include <optional>
-
-
-
-
-
- template <typename Container>
- std::optional<typename Container::mapped_type>
- lookup(const typename Container::key_type& key, const Container& kv) {
- typename Container::const_iterator p = kv.find(key);
- if (p != kv.end())
- return std::optional<typename Container::mapped_type>(p->second);
- else
- return std::optional<typename Container::mapped_type>();
- }
-
-
-
-
-
-
-
-
- template <typename Val1, typename Fn>
- typename std::invoke_result<Fn, Val1>::type
- operator >= (const std::optional<Val1>& maybe_x, const Fn& fn) {
- if (maybe_x)
- return fn(*maybe_x);
- else {
-
- return std::optional<
- typename std::invoke_result<Fn, Val1>::type::value_type >();
- }
- }
使ってみます。
C++
- #include "c++maybe2.h"
- #include <map>
- #include <unordered_map>
- #include <cstdio>
- #include <string>
- #include <functional>
- using namespace std;
-
- void put_optional(const optional<string>& x) {
- if (x)
- printf("value = \"%s\"\n", x->c_str());
- else
- printf("value = Nothing\n");
- }
-
- typedef map<int, string> L2;
- typedef unordered_map<int, L2> L1;
-
- int main() {
- L1 db;
- L2 v1;
- v1.insert(pair<int, string>(10, "foo"));
- v1.insert(pair<int, string>(20, "bar"));
- db.insert(pair<int, L2>(1, v1));
-
- L2 v2;
- v2.insert(pair<int, string>(2, "hoge"));
- v2.insert(pair<int, string>(3, "hogehoge"));
- db.insert(pair<int, L2>(50, v2));
-
-
- optional<L2> x = lookup<L1>(1, db);
- if (x) {
- optional<string> y = lookup<L2>(10, *x);
- put_optional(y);
- }
- else
- printf("v = Nothing\n");
-
-
- optional<string> y = lookup<L1>(50, db) >= bind(lookup<L2>, 2, placeholders::_1);
- put_optional(y);
-
-
- optional<string> z = lookup<L1>(11, db) >= bind(lookup<L2>, 2, placeholders::_1);
- put_optional(z);
-
- return 0;
- }
ついで: Either
モナドっぽく
C++17 std::variant
がまんま, Haskell Either
です。
C++
- #include <variant>
- #include <string>
- #include <iostream>
- using namespace std;
-
-
-
- template <typename LeftT, typename RightT, typename Fn>
- typename invoke_result<Fn, RightT>::type
- operator >= (const variant<LeftT, RightT>& either_x, const Fn& fn) {
- if (either_x.index() == 1)
- return fn( get<1>(either_x) );
- else {
-
-
- using ResultVariant = typename invoke_result<Fn, RightT>::type;
- variant<LeftT, variant_alternative_t<1, ResultVariant> > ret;
- return ret.template emplace<0>( get<0>(either_x) );
- }
- }
-
- struct Hoge { int xx; };
-
-
- variant<string, Hoge> func(int x) {
- variant<string, Hoge> ret;
- Hoge z = {.xx = x * 200};
- return ret.emplace<1>(z);
- }
-
- int main() {
-
- variant<string, int> v;
- v.emplace<1>(25);
-
- v >= &func >= [](const Hoge& r) {
- cout << r.xx << "\n";
- return variant<string, float>();
- };
- return 0;
- }
サイト内関連ページ
- はすけるで遊ぶ
- Haskellのメモがいくつか。
外部リンク
- C++でMaybeモナドを返すlookup関数を作ってみた - Faith and Brave - C++で遊ぼう
- こちらのほうが高度です。
- C++ for Haskeller
- ネタ。