10

(まず、質問の「バインド」は関係ありませんstd::bind

私はExpected<T>の話を見て、このテクニックの歴史に関するプレゼンテーションには、Haskellでのこのことの背後にある核となるアイデアが欠けていると思いました。

Haskellの核となる考え方は、の値に「決して」アクセスしないということですExpected<T>Expected<T>代わりに、の状態に応じて適用されるかどうかに応じて、ラムダをに渡しますExpected<T>

この「バインド」コンビネータExpected<T>が使用される主な方法であると予想していたので、このプログラミングスタイルが何らかの理由で拒否されたかどうかを確認する必要があります。そのコンビネータthenを次のように呼びます。

template <class T> class Expected<T> {
    ....
    template <class V, class F> Expected<V> then(F fun_) {
       if (!valid()) {
           return Expected<V>::fromException(this(??)); // something like that
       }
       return fun_(get());
    }
}

このコンビネータのポイントは、エラーをチェックする必要がなく、最初に失敗した関数が評価を短絡させる関数のリストを連鎖させることです。

auto res = Expected<Foo>::fromCode([]() { return callFun1(...); })
             .then([](Baz& val) { return callFun2(..,val,..); })
             .then([](Bar& val) { return callFun3(val,...); });

>>=または、Haskellで使用されている演算子に似始めているこの構文。

auto res = []() { return callFun1(...); }
           >> [](Baz& val) { return callFun2(..,val,..); }
           >> [](Bar& val) { return callFun3(val,...); };    

callFun1を返し、Expected<Baz>callFun2返しExpected<Bar>、をcallFun3返しますExpected<Foo>

ご覧のとおり、このコードはエラーをチェックしません。エラーは実行を停止しますが、それでも。のすべての利点がありExpected<T>ます。Eitherこれは、 Haskellでモナドを使用する標準的な方法です。

私が言ったように、確かに誰かがこれを見たに違いありません。

編集:callFun{1..3}に対して間違ったリターンタイプを記述しました。のさまざまな値ではExpected<T>なく、を返します。これは、 orコンビネータの要点のようなものです。TTthen>>

4

2 に答える 2

6

.thenHaskellとは対照的に、C ++の関数テンプレート(たとえば、your)に通常の関数を渡すことは非常にイライラします。それらがオーバーロードされている場合、またはテンプレートである場合は、それらに明示的な型署名を提供する必要があります。これは醜く、単調な計算チェーンには向いていません。

また、現在のラムダは単形であるため、パラメータータイプを明示的に入力する必要があります。これにより、この状況全体がさらに悪化します。

C ++で関数型プログラミングを簡単にするための多くの(ライブラリ)試みがありましたが、それは常にこれらの2つのポイントに戻ります。

最後になりましたが、C ++での機能スタイルのプログラミングは標準ではなく、その概念が完全に異質である多くの人々がいますが、「リターンコード」のような概念は理解しやすいものです。

.then関数テンプレートのVパラメーターは明示的に指定する必要がありますが、それは比較的簡単に修正できることに注意してください。)

于 2013-03-14T15:20:58.787 に答える
2

私自身の質問に答えて、さらに情報を提供し、私の実験を文書化します。

私は切断しExpected<T>ました。私がしたことは、名前get()thenReturn()付けて使用しないように名前を変更することでした。全体の名前を変更しましたeither<T>

そして、then(...)関数を追加しました。結果はそれほど悪いとは思いませんが(おそらく多くのバグを除いて)、それthen はモナディックバインドではないことを指摘する必要があります。モナディックバインドは関数合成の変形であるため、2つの関数を操作して、関数を返します。 可能であればthen、関数をに適用するだけです。either

私たちが得るものは

// Some template function we want to run.
// Notice that all our functions return either<T>, so it
// is "discouraged" to access the wrapped return value directly.
template <class T>
auto square(T num) -> either<T> 
{
    std::cout << "square\n";
    return num*num;
}

// Some fixed-type function we want to run.
either<double> square2(int num) 
{
    return num*num;
}

// Example of a style of programming.
int doit() 
{
    using std::cout;
    using std::string;
    auto fun1 = [] (int x)    -> either<int>    { cout << "fun1\n"; throw "Some error"; };
    auto fun2 = [] (int x)    -> either<string> { cout << "fun2\n"; return string("string"); };
    auto fun3 = [] (string x) -> either<int>    { cout << "fun3\n"; return 53; };
    int r = either<int>(1)
        .then([] (int x)    -> either<double> { return x + 1; })
        .then([] (double x) -> either<int>    { return x*x; })
        .then(fun2) // here we transform to string and back to int.
        .then(fun3)
        .then(square<int>)  // need explicit disambiguation
        .then(square2)
        .thenReturn();
    auto r2 = either<int>(1)
        .then(fun1)  // exception thrown here
        .then(fun2)  // we can apply other functions,
        .then(fun3); // but they will be ignored
    try {
        // when we access the value, it throws an exception.
        cout << "returned : " << r2.thenReturn();
    } catch (...) {
        cout << "ouch, exception\n";
    }
    return r;
}

完全な例を次に示します。

#include <exception>
#include <functional>
#include <iostream>
#include <stdexcept>
#include <type_traits>
#include <typeinfo>
#include <utility>

template <class T> class either {
    union {
        T ham;
        std::exception_ptr spam;
    };
    bool got_ham;
    either() {}
    // we're all friends here
    template<typename> friend class either;
public:
    typedef T HamType;
    //either(const T& rhs) : ham(rhs), got_ham(true) {}
    either(T&& rhs) : ham(std::move(rhs)), got_ham(true) {}
    either(const either& rhs) : got_ham(rhs.got_ham) {
        if (got_ham) {
            new(&ham) T(rhs.ham);
        } else {
            new(&spam) std::exception_ptr(rhs.spam);
        }
    }
    either(either&& rhs) : got_ham(rhs.got_ham) {
        if (got_ham) {
            new(&ham) T(std::move(rhs.ham));
        } else {
            new(&spam) std::exception_ptr(std::move(rhs.spam));
        }
    }
    ~either() {
        if (got_ham) {
            ham.~T();
        } else {
            spam.~exception_ptr();
        }
    }
    template <class E>
    static either<T> fromException(const E& exception) {
        if (typeid(exception) != typeid(E)) {
            throw std::invalid_argument("slicing detected");
        }
        return fromException(std::make_exception_ptr(exception));
    }
    template <class V>
    static either<V> fromException(std::exception_ptr p) {
        either<V> result;
        result.got_ham = false;
        new(&result.spam) std::exception_ptr(std::move(p));
        return result;
    }
    template <class V>
    static either<V> fromException() {
        return fromException<V>(std::current_exception());
    }
    template <class E> bool hasException() const {
        try {
            if (!got_ham) std::rethrow_exception(spam);
        } catch (const E& object) {
            return true;
        } catch (...) {
        }
        return false;
    }
    template <class F>
    auto then(F fun) const -> either<decltype(fun(ham).needed_for_decltype())> {
        typedef decltype(fun(ham).needed_for_decltype()) ResT;
        if (!got_ham) {
            either<ResT> result;
            result.got_ham = false;
            result.spam = spam;
            return result;
        }
        try {
            return fun(ham);
        } catch (...) {  
            return fromException<ResT>();
        }
    }
    T& thenReturn() {
        if (!got_ham) std::rethrow_exception(spam);
        return ham;
    }
    const T& thenReturn() const {
        if (!got_ham) std::rethrow_exception(spam);
        return ham;
    }
    T needed_for_decltype();
};

template <class T>
auto square(T num) -> either<T> 
{
    std::cout << "square\n";
    return num*num;
}

either<double> square2(int num) 
{
    return num*num;
}

int doit() 
{
    using std::cout;
    using std::string;
    auto fun1 = [] (int x)    -> either<int>    { cout << "fun1\n"; throw "Some error"; };
    auto fun2 = [] (int x)    -> either<string> { cout << "fun2\n"; return string("string"); };
    auto fun3 = [] (string x) -> either<int>    { cout << "fun3\n"; return 53; };
    int r = either<int>(1)
        .then([] (int x)    -> either<double> { return x + 1; })
        .then([] (double x) -> either<int>    { return x*x; })
        .then(fun2) // here we transform to string and back to int.
        .then(fun3)
        .then(square<int>)  // need explicit disambiguation
        .then(square2)
        .thenReturn();
    auto r2 = either<int>(1)
        .then(fun1)  // exception thrown here
        .then(fun2)  // we can apply other functions,
        .then(fun3); // but they will be ignored
    try {
        // when we access the value, it throws an exception.
        cout << "returned : " << r2.thenReturn();
    } catch (...) {
        cout << "ouch, exception\n";
    }
    return r;
}


int main() {
    using std::cout;
    doit();
    cout << "end. ok";
}
于 2013-03-14T23:23:36.207 に答える