2013-03-14 7 views
10

(Prima di tutto "legare" in questione non ha nulla a che fare con std::bind)È chiunque usando monadica programmazione bind-stile con Previsto <T>

ho guardato il Expected<T> talk e ho pensato che la presentazione sulla storia di questo alla tecnica mancava l'idea alla base di questa cosa in Haskell.

L'idea principale di Haskell è che non si "accederà mai" al valore di uno Expected<T>. Quello che fai invece è passare un lambda allo Expected<T> che verrà applicato o meno a seconda dello stato dello Expected<T>.

Mi sarei aspettato che questo combinatore "bind" fosse il metodo principale che sarebbe stato utilizzato Expected<T>, quindi devo chiedere se questo stile di programmazione è stato rifiutato per qualche motivo. Chiamerò che Combinator then nel seguente:

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()); 
    } 
} 

Il punto di questo combinatore è a catena un elenco di funzioni in cui si non si necessità di verificare la presenza di errori, e dove la prima funzione che non riesce cortocircuiterà la valutazione.

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

O questa sintassi che sta cominciando ad assomigliare l'operatore >>= che viene utilizzato in Haskell.

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

callFun1 restituisce una Expected<Baz>, callFun2 restituisce un Expected<Bar>, e callFun3 restituisce un Expected<Foo>.

Come si può vedere, questo codice non controlla la presenza di errori. Gli errori interromperanno l'esecuzione, ma hanno ancora tutti i vantaggi di Expected<T>. Questo è il modo standard per usare la monade Either in Haskell.

Come ho detto, sicuramente qualcuno deve averlo guardato.

Modifica: Ho scritto tipi di reso errati per callFun {1..3}. Restituiscono Expected<T>, non T per vari valori di T. Questo è in qualche modo il punto intero del combinatore then o >>.

risposta

5

Passare le normali funzioni ai modelli di funzione (ad esempio, il tuo .then) in C++, al contrario di Haskell, è estremamente frustrante. Devi fornire una firma di tipo esplicita per loro se sono sovraccarichi o modelli. Questo è brutto e non si presta a catene di computazione monadiche.

Inoltre, i nostri lambda correnti sono monomorphic, è necessario digitare esplicitamente i tipi di parametri, il che rende l'intera situazione ancora peggiore.

Ci sono state molte (librerie) per rendere più semplice la programmazione funzionale in C++, ma torna sempre su questi due punti.

Ultimo ma non meno importante, la programmazione in stile funzionale in C++ non è la norma e ci sono molte persone alle quali questo concetto è completamente estraneo, mentre un concetto di "codice di ritorno" è facile da capire.

(Si noti che il parametro del modello di funzione .thenV deve essere specificato in modo esplicito, ma che è relativamente facile risolvibile.)

+0

Potrebbe ampliare la risposta con un esempio di best-possibile sintassi che si può ottenere in C++ per questo? – user239558

+0

@user: 'some_expected.then (static_cast (& some_overloaded_function))', o con lambda, che, essendo monomorfici, possono essere visti come un cast sotto mentite spoglie. : | – Xeo

+0

questo è un po 'sopra la mia testa, ma tutti e due potresti voler controllare la lezione di bartosz: https://www.youtube.com/watch?v=ph7qt0pkPkc disclaimer: come molte volte quando si tratta di FP ho avuto una sensazione: o non è così speciale o mi manca qualcosa – NoSenseEtAl

2

Rispondendo alla mia domanda proprio per dare qualche informazione in più e documentare il mio esperimento:

I mutilato Expected<T>. Quello che ho fatto è stato rinominare get() a thenReturn() per scoraggiarne l'uso attraverso la denominazione. Ho ribattezzato il tutto either<T>.

E poi ho aggiunto la funzione then(...). Non penso che il risultato sia così brutto (eccetto probabilmente un sacco di bug), ma devo sottolineare che thennon è vincolante monadico. Il binding monadico è una variante della composizione della funzione, quindi si opera su due funzioni e si restituisce una funzione. then applica semplicemente una funzione a either, se possibile.

Quello che otteniamo è

// 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; 
} 

Ecco un esempio completo:

#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"; 
} 
+0

o in got_ham city o poi in eccezione land-a, ma questo è solo spam. –

Problemi correlati