在C ++中表达一般的monadic接口(如Monad类)

是否甚至有可能表达一种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;
}

但面临缺乏多态性。 实际上,它可能是我8年前上一次使用它的原因不明的C ++。 可能有可能使用一些新的C ++特性来表示一般的monadic接口,如类型推断。 这只是为了好玩,也是为了解释非单身汉和非数学家的单子。


C ++'类型系统的功能不足以抽象出更高级的类型,但由于模板是鸭子类型的,因此您可以忽略它,只是单独实现各种单元,然后将单元操作表示为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]); })东西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_typeMonadicValue (内部bind )都不是monad,但是对于总体monad的一些just_type<int>值而言类型 - just_type<int>nothing_type一起是monad(某种- 我现在抛开了类似的问题,但请记住,可以在事实之后重新绑定模板特化,例如std::allocator<T> !)。 因为这样的bind在接受它时必须稍微宽松,但要注意这并不意味着它必须接受一切。

当然,完全有可能有一个类模板M ,使得M<T>MonadicValue的模型,并且bind(m, f)只有类型M<U> ,其中m类型为M<T> 。 这在某种意义上使M成为单子(具有* -> * ),并且代码仍然可以工作。 (并且在谈到Maybe ,也许适应boost::optional<T>有一个monadic接口是一个很好的练习。)

敏锐的读者会注意到,我没有相当于这里的return ,所有事情都是由Just构造函数的justmake_just工厂完成的。 这是为了保持答案的简短 - 一种可能的解决方案是编写一个完成return工作的pure ,并返回一个可隐式转换为模型MonadicValue任何类型的MonadicValue (例如推迟一些MonadicValue::pure )。

有一些设计上的考虑因素,因为C ++的有限类型推导意味着bind(pure(4), [](int) { return pure(5); })将不起作用。 然而,这并不是一个无法克服的问题。 (解决方案的一些概要是重载bind ,但如果我们添加到我们的MonadicValue概念的接口,这是不方便的,因为任何新操作都必须能够显式处理纯值;或者将MonadicValue一个纯粹值作为一个模型作为好。)


我会这样做:

template<class T>
class IO {
public:
   virtual T get() const=0;
};

template<class T, class K>
class C : public IO<K> {
public:
   C(IO<T> &io1, IO<K> &io2) : io1(io1), io2(io2) { }
   K get() const { 
      io1.get();
      return io2.get();
   }
private:
  IO<T> &io1;
  IO<K> &io2;
};

int main() {
  IO<float> *io = new YYYY;
  IO<int> *io2 = new XXX;
  C<float,int> c(*io, *io2);
  return c.get();
}
链接地址: http://www.djcxy.com/p/11533.html

上一篇: Express general monadic interface (like Monad class) in C++

下一篇: Reactive Extensions: buffer until subscriber is idle