Монады в C++
От: frogkiller Россия  
Дата: 20.09.07 14:03
Оценка:
В статье в LinuxJournal приводится оригинальная интерпретация одной из концепций ФП — монад. Но все примеры были на языках, поддерживающих замыкания. В ожидании С++0х со встроенной их поддержкой попробовал переписать примеры "руками". Извиняюсь за обилие кода, меньше не получилось

#include <iostream>

template <typename TValue>
class CMonad;

template <typename TValue>
class CMonad
{
public:
    typedef CMonad<TValue> TRetInst;
    typedef TValue           TArgInst;

protected:
    TValue m_value;
    CMonad() {}

public:
    struct CMyFunctor
    {
        virtual TRetInst * apply(const TArgInst &) = 0;
    };

    CMonad(const TValue &_value) : m_value(_value) {}

    virtual TRetInst * pass(CMyFunctor * pfn)
    {
        return pfn->apply(m_value);
    }

    virtual ~CMonad() {}

    virtual void Print() const = 0;
};

template <class TValue>
struct IArithmetic
{
    typedef CMonad<TValue> TRetInst;

    template <typename TInstance>
    class CAddFunctor : public CMonad<TValue>::CMyFunctor
    {
        const TValue & m_value;
    public:
        CAddFunctor(const TValue &_value) : m_value(_value) {}
        virtual TRetInst * apply(const TValue &_value)
        {
            return new TInstance(m_value + _value);
        }
    };

    template <template <typename> class CApplication, typename TInstance>
    class CMyBinder : public CMonad<TValue>::CMyFunctor
    {
        TRetInst * m_value;
    public:
        CMyBinder(TRetInst * _value) : m_value(_value) {}
        virtual TRetInst * apply(const TValue &_value)
        {
            CApplication<TInstance> app(_value);
            return m_value->pass(&app);
        }
    };

    template <typename TInstance>
    IArithmetic<TValue> * Add(TInstance &rhs)
    {
        CMyBinder<CAddFunctor, TInstance> app(dynamic_cast<TRetInst *>(&rhs));
        return dynamic_cast<IArithmetic<TValue> *>(dynamic_cast<CMonad<TValue>*>(this)->pass(&app));
    }
    virtual ~IArithmetic() {}
};

template <typename TValue, typename TInstance>
class CMaybe : public CMonad<TValue>
{
    bool m_bNone;

public:
    struct None {};
    static None & getNone()
    {
        static None none;
        return none;
    }

    typedef CMonad<TValue> IParent;

    CMaybe(const TValue &_value) : IParent(_value), m_bNone(false) {}
    CMaybe(const None &) : m_bNone(true) {}

    virtual typename IParent::TRetInst * pass(typename IParent::CMyFunctor * pfn)
    {
        if (m_bNone)
        {
            return new TInstance(getNone());
        }
        return IParent::pass(pfn);
    }

    virtual void Print() const
    {
        if (m_bNone)
        {
            std::cout << "Maybe(none)" << std::endl;
        }
        else
        {
            std::cout << "Maybe(" << this->m_value << ")" << std::endl;
        }
    }
};

template <typename TValue, typename TInstance>
class CMaybe2 : public CMonad<TValue>
{
    bool m_bNone;

public:
    struct None {};
    static None & getNone()
    {
        static None none;
        return none;
    }

    typedef CMonad<TValue> IParent;

    CMaybe2(const TValue &_value) : IParent(_value), m_bNone(false) {}
    CMaybe2(const None &) : m_bNone(true) {}

    virtual typename IParent::TRetInst * pass(typename IParent::CMyFunctor * pfn)
    {
        if (m_bNone)
        {
            return new TInstance(TValue());
        }
        return IParent::pass(pfn);
    }

    virtual void Print() const
    {
        std::cout << "Maybe(" << this->m_value << ")" << std::endl;
    }
};

template <typename T>
struct CArithmeticMaybe : public CMaybe<T, CArithmeticMaybe<T> >, public IArithmetic<T>
{
    typedef CMaybe<T, CArithmeticMaybe<T> > CMaybeParent;
    CArithmeticMaybe(const typename CMaybeParent::None &none) : CMaybeParent(none) {}
    CArithmeticMaybe(const T &_value) : CMaybeParent(_value) {}
};

template <typename T>
struct CArithmeticMaybe2 : public CMaybe2<T, CArithmeticMaybe2<T> >, public IArithmetic<T>
{
    typedef CMaybe2<T, CArithmeticMaybe2<T> > CMaybeParent;
    CArithmeticMaybe2(const typename CMaybeParent::None &none) : CMaybeParent(none) {}
    CArithmeticMaybe2(const T &_value) : CMaybeParent(_value) {}
};

typedef CArithmeticMaybe<int> CMaybeInt;
typedef CArithmeticMaybe2<int> CMaybeInt2;

CMaybeInt n1(5), n2(6), n3(CMaybeInt::getNone());
CMaybeInt2 m1(5), m2(6), m3(CMaybeInt2::getNone());

int main(int argc, char* argv[])
{
    dynamic_cast<CMonad<int>*>(n1.Add(n2)->Add(n2))->Print();
    dynamic_cast<CMonad<int>*>(n1.Add(n2)->Add(n3))->Print();

    std::cout << "--------------------" << std::endl;
    dynamic_cast<CMonad<int>*>(m1.Add(m2)->Add(m2))->Print();
    dynamic_cast<CMonad<int>*>(m1.Add(m2)->Add(m3))->Print();
    dynamic_cast<CMonad<int>*>(m3.Add(m2)->Add(m1))->Print();

    // все ухищрения с dynamic_cast ради вот такой возможности
    // связывать монады разных типов
    std::cout << "--------------------" << std::endl;
    dynamic_cast<CMonad<int>*>(m1.Add(n2)->Add(m3))->Print();
    dynamic_cast<CMonad<int>*>(m1.Add(m2)->Add(n2))->Print();
    dynamic_cast<CMonad<int>*>(m3.Add(n2)->Add(m2))->Print();

    return 0;
}


Что мне не нравится:
1. Приходится постоянно выносить действительное имя класса в качестве шаблонного параметра, чтобы иметь возможность создавать объект нужного класса.
2. Чтобы связывать монады разных типов, приходится постоянно использовать dynamic_cast.

Профи буста, подскажите, пожалуйста, можно ли добиться аналогичного эффекта в статике? А главное, как это сделать...

PS. Про утечку памяти можно мне не объяснять, я и сам знаю, где она появляется, и как это лечится.
PPS. Подозреваю, что изобрёл велоспед, и если это так, не пинайте слишком сильно
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Курица — это инструмент, с помощью которого одно яйцо производит другие.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.