c++ - 在 C++ 中表达通用 monadic 接口(interface)(如 Monad 类)

标签 c++ haskell monads

是否有可能表达一种 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_typeMonadicValue (在 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_justJust对应的工厂构造函数。这是为了让答案简短——一个可能的解决方案是写一个 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/

相关文章:

c++ - libstdc++ 已弃用;移至 libc++ [-Wdeprecated] 但更改会产生编译错误

unit-testing - 属性测试用于什么

haskell - 将 StateT 移入和移出 IO

haskell - 为什么 monad 类型类中应该存在 fail 方法?

列表到元组计数值重复和元组内的列表 - Haskell

haskell - Arrows 能做哪些 Monads 做不到的事情?

c++ - Qt5 C++ : Set Spinbox Delegate For Specific Table Column

C++继承函数覆盖

c++ - Apple LLVM 6.0 预处理器神奇#if defined()

c - hsc2hs:用 Haskell 改变 C 结构