C++ 的类型系统不足以抽象出更高种类的类型,但由于模板是鸭子类型的,您可以忽略这一点,只需分别实现各种 Monad,然后将 monadic 操作表示为 SFINAE 模板。丑陋,但它得到了最好的。
这个评论简直是赚到了钱。我一次又一次地看到人们试图使模板专业化“协变”和/或滥用继承。无论好坏,在我看来,面向概念的泛型编程更明智*。这是一个快速演示,它将使用 C++11 的特性来简洁明了,尽管应该可以在 C++03 中实现相同的功能:
(*:对于竞争意见,请参阅我的引文中的“丑陋,但最好的”!)
#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
-qualified 和 move-only 类型就会出现错误。您可以在此处查看正在运行的代码(使用 GCC 4.7),尽管这可能是用词不当,因为它所做的只是触发断言。(对于未来的读者,ideone 上的代码相同。)
解决方案的核心是just_type<T>
,nothing_type
或MonadicValue
(inside bind
) 都不是 monad,而是总体 monad 的某些 monadic 值的类型——just_type<int>
并且nothing_type
一起是 monad现在,但请记住,可以在事后重新绑定模板特化,例如 for std::allocator<T>
!)。因此,bind
它必须对它接受的内容有所宽容,但请注意这并不意味着它必须接受一切。
当然,完全有可能拥有一个类模板M
,它M<T>
是一个模型,MonadicValue
并且bind(m, f)
只有在有 type 的M<U>
地方m
才有 type M<T>
。从某种意义上说,这会M
生成 monad(使用 kind * -> *
),并且代码仍然可以工作。(说到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
。)