C++ の型システムは、より高次の型を抽象化するほど強力ではありませんが、テンプレートはダック型であるため、これを無視してさまざまなモナドを個別に実装し、モナド操作を SFINAE テンプレートとして表現することができます。醜いですが、最高です。
このコメントは大金です。テンプレートの特殊化を「共変」にしようとしたり、継承を悪用したりしようとしている人を何度も目にします。良くも悪くも、概念指向のジェネリック プログラミングは、私の意見では*より正気です。C++03 でも同じ機能を実装できるはずですが、簡潔さと明確さのために C++11 の機能を使用する簡単なデモを次に示します。
(*: 競合する意見については、私の引用の「醜いが、最高の結果」を参照してください!)
#include <utility>
#include <type_traits>
// SFINAE utility
template<typename...> struct void_ { using type = void; };
template<typename... T> using Void = typename void_<T...>::type;
/*
* In an ideal world std::result_of would just work instead of all that.
* Consider this as a write-once (until std::result_of is fixed), use-many
* situation.
*/
template<typename Sig, typename Sfinae = void> struct result_of {};
template<typename F, typename... Args>
struct result_of<
F(Args...)
, Void<decltype(std::declval<F>()(std::declval<Args>()...))>
> {
using type = decltype(std::declval<F>()(std::declval<Args>()...));
};
template<typename Sig> using ResultOf = typename result_of<Sig>::type;
/*
* Note how both template parameters have kind *, MonadicValue would be
* m a, not m. We don't whether MonadicValue is a specialization of some M<T>
* or not (or derived from a specialization of some M<T>). Note that it is
* possible to retrieve the a in m a via typename MonadicValue::value_type
* if MonadicValue is indeed a model of the proper concept.
*
* Defer actual implementation to the operator() of MonadicValue,
* which will do the monad-specific operation
*/
template<
typename MonadicValue
, typename F
/* It is possible to put a self-documenting assertion here
that will *not* SFINAE out but truly result in a hard error
unless some conditions are not satisfied -- I leave this out
for brevity
, Requires<
MonadicValueConcept<MonadicValue>
// The two following constraints ensure that
// F has signature a -> m b
, Callable<F, ValueType<MonadicValue>>
, MonadicValueConcept<ResultOf<F(ValueType<MonadicValue>)>>
>...
*/
>
ResultOf<MonadicValue(F)>
bind(MonadicValue&& value, F&& f)
{ return std::forward<MonadicValue>(value)(std::forward<F>(f)); }
// Picking Maybe as an example monad because it's easy
template<typename T>
struct just_type {
using value_type = T;
// Encapsulation omitted for brevity
value_type value;
template<typename F>
// The use of ResultOf means that we have a soft contraint
// here, but the commented Requires clause in bind happens
// before we would end up here
ResultOf<F(value_type)>
operator()(F&& f)
{ return std::forward<F>(f)(value); }
};
template<typename T>
just_type<T> just(T&& t)
{ return { std::forward<T>(t) }; }
template<typename T>
just_type<typename std::decay<T>::type> make_just(T&& t)
{ return { std::forward<T>(t) }; }
struct nothing_type {
// Note that because nothing_type and just_type<T>
// are part of the same concept we *must* put in
// a value_type member type -- whether you need
// a value member or not however is a design
// consideration with trade-offs
struct universal { template<typename T> operator T(); };
using value_type = universal;
template<typename F>
nothing_type operator()(F const&) const
{ return {}; }
};
constexpr nothing_type nothing;
のような書き方が可能ですbind(bind(make_just(6), [](int i) { return i - 2; }), [](int i) { return just("Hello, World!"[i]); })
。ラップされた値が適切に転送されないという点で、この投稿のコードは不完全であることに注意してくださいconst
。コードの動作 (GCC 4.7 を使用)はこちらで確認できますが、アサーションをトリガーするだけではないため、誤った呼び名である可能性があります。(将来の読者のために ideone の同じコード。)
解決策の核心は、just_type<T>
, nothing_type
or MonadicValue
(内部) のどれもモナドではなく、包括的なモナドbind
のいくつかのモナド値の型であるということです.しかし、例えばテンプレートの特殊化を後から再バインドすることも可能であることに注意してください ( !)。そのため、何を受け入れるかについては多少寛容でなければなりませんが、それがすべてを受け入れなければならないという意味ではないことに注意してください。just_type<int>
nothing_type
std::allocator<T>
bind
もちろん、 のモデルであり、has type の type しか持たないM
ようなクラス テンプレートを持つことは完全に可能です。これはある意味で( kind を使用して) モナドを作成し、コードは引き続き機能します。(そして について言えば、おそらくモナディックなインターフェースを持つように適応させることは良い練習になるでしょう。)M<T>
MonadicValue
bind(m, f)
M<U>
m
M<T>
M
* -> *
Maybe
boost::optional<T>
return
賢明な読者は、私がここに相当するものを持っていないことに気づいたでしょう。すべては、コンストラクターに対応するjust
およびmake_just
ファクトリーで行われます。Just
これは答えを短くするためです。考えられる解決策pure
は、 の仕事をする を記述しreturn
、モデル化する任意の型に暗黙的に変換可能な値を返すことですMonadicValue
(たとえば、 some を延期することによってMonadicValue::pure
)。
ただし、C++ の制限された型推定はそのままでbind(pure(4), [](int) { return pure(5); })
は機能しないことを意味するという点で、設計上の考慮事項があります。しかし、それは克服できない問題ではありません。(解決策のいくつかの概要はオーバーロードすることですが、新しい操作も純粋な値を明示的に処理できる必要があるためbind
、概念のインターフェイスに追加すると不便です。または、純粋な値をモデルにすることもできます。)MonadicValue
MonadicValue