是否有可能表达一种 monad"C++? 我开始写这样的东西,但卡住了:
#include <iostream>
template <typename a, typename b> struct M;
template <typename a, typename b> struct M {
virtual M<b>& operator>>( M<b>& (*fn)(M<a> &m, const a &x) ) = 0;
};
template <typename a, typename b>
struct MSome : public M<a> {
virtual M<b>& operator>>( M<a>& (*fn)(M<a> &m, const a &x) ) {
return fn(*this, x);
}
private:
a x;
};
M<int, int>& wtf(M<int> &m, const int &v) {
std::cout << v << std::endl;
return m;
}
int main() {
// MSome<int> v;
// v >> wtf >> wtf;
return 0;
}
但面临缺乏多态性。实际上它可能是我的非当前 C++,因为我上次使用它是 8 年前的事。可能有可能使用一些新的 C++ 功能(如类型推断)来表达通用的 monadic 接口(interface)。这只是为了好玩,也是为了向非 haskellers 和非数学家解释 monad。
最佳答案
C++' type system is not powerful enough to abstract over higher-kinded types, but since templates are duck-typed you may ignore this and just implement various Monads seperately and then express the monadic operations as SFINAE templates. Ugly, but the best it gets.
这条评论很划算。我一次又一次地看到人们试图使模板特化“协变”和/或滥用继承。无论好坏,在我看来,面向概念的泛型编程*更明智。这里有一个快速演示,为了简洁明了,将使用 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
就会出现错误。涉及限定和仅移动类型。您可以查看实际代码(使用 GCC 4.7)here ,尽管这可能用词不当,因为它所做的只是不会触发断言。 (Same code on ideone 供 future 的读者使用。)
解决方案的核心是just_type<T>
都没有, nothing_type
或 MonadicValue
(在 bind
内)是 monad,但是是总体 monad 的某些 monadic 值的类型 -- just_type<int>
和 nothing_type
在一起 是一个 monad(有点——我现在把种类问题放在一边,但请记住,可以在事后重新绑定(bind)模板特化,例如 std::allocator<T>
! ).因此 bind
必须对其接受的内容有所宽容,但请注意这并不意味着它必须接受所有内容。
当然完全有可能有一个类模板M
这样 M<T>
是 MonadicValue
的模型和 bind(m, f)
只有类型 M<U>
其中 m
类型为 M<T>
.这在某种意义上会使M
monad(种类 * -> *
),代码仍然有效。 (说到 Maybe
,也许调整 boost::optional<T>
以拥有一个单子(monad)界面将是一个很好的练习。)
精明的读者会注意到我没有 return
的等价物在这里,一切都是用 just
完成的和 make_just
与Just
对应的工厂构造函数。这是为了让答案简短——一个可能的解决方案是写一个 pure
完成 return
的工作, 并返回一个值,该值可隐式转换为建模 MonadicValue
的任何类型(例如通过推迟一些 MonadicValue::pure
)。
尽管 C++ 的有限类型推导意味着 bind(pure(4), [](int) { return pure(5); })
存在设计注意事项开箱即用。然而,这并不是一个无法克服的问题。 (一些解决方案的概述是重载 bind
,但是如果我们添加到 MonadicValue
概念的接口(interface),那会很不方便,因为任何新操作也必须能够显式处理纯值;或者使纯值成为MonadicValue
的型号。)
关于c++ - 在 C++ 中表达通用 monadic 接口(interface)(如 Monad 类),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13758524/