Leniwa ocena w C++

C++ nie ma natywnego wsparcia dla leniwej oceny (tak jak Haskell).

Zastanawiam się, czy jest możliwe zaimplementowanie leniwej oceny w C++ w rozsądny sposób. Jeśli tak, to jak byś to zrobił?

EDIT: podoba mi się odpowiedź Konrada Rudolfa.

Zastanawiam się, czy jest możliwe zaimplementowanie go w bardziej ogólny sposób, na przykład za pomocą parametryzowanej klasy lazy, która zasadniczo działa dla t, tak jak matrix_add działa dla matrix.

Każda operacja na T wróć leniwy zamiast. Jedynym problemem jest przechowywanie argumentów i kodu operacji wewnątrz samego lazy ' a. Czy ktoś widzi jak to poprawić?

Author: nicael, 2009-01-05

12 answers

Zastanawiam się, czy jest możliwe zaimplementowanie leniwej oceny w C++ w rozsądny sposób. Jeśli tak, to jak byś to zrobił?

Tak, jest to możliwe i dość często robione, np. dla obliczeń macierzy. Głównym mechanizmem ułatwiającym to jest przeciążenie operatora. Rozważmy przypadek dodawania macierzy. Podpis funkcji zwykle wygląda mniej więcej Tak:

matrix operator +(matrix const& a, matrix const& b);

Teraz, aby ta funkcja była leniwa, wystarczy zwrócić proxy zamiast rzeczywisty wynik:

struct matrix_add;

matrix_add operator +(matrix const& a, matrix const& b) {
    return matrix_add(a, b);
}

Teraz wystarczy napisać ten proxy:

struct matrix_add {
    matrix_add(matrix const& a, matrix const& b) : a(a), b(b) { }

    operator matrix() const {
        matrix result;
        // Do the addition.
        return result;
    }
private:
    matrix const& a, b;
};

Magia polega na metodzie operator matrix(), która jest implicit operatorem konwersji z matrix_add na zwykły matrix. W ten sposób można łączyć wiele operacji (oczywiście zapewniając odpowiednie przeciążenia). Ocena odbywa się tylko wtedy, gdy końcowy wynik jest przypisany do instancji matrix.

EDIT powinienem być bardziej wyraźny. Jak to jest, kod nie ma sensu bo chociaż ocena dzieje się leniwie, to i tak dzieje się w tym samym wyrażeniu. W szczególności, inny dodatek będzie oceniał ten kod, chyba że struktura matrix_add zostanie zmieniona, aby umożliwić dodawanie łańcuchowe. C++0x znacznie ułatwia to, zezwalając na zmienne szablony (np. listy szablonów o zmiennej długości).

Jednak jeden bardzo prosty przypadek, w którym kod ten miałby rzeczywistą, bezpośrednią korzyść, jest następujący:]}
int value = (A + B)(2, 3);

Tutaj przyjmuje się, że A i B są macierze dwuwymiarowe i że dereferencja odbywa się w notacji Fortran, tzn. powyższe wylicza jeden element z sumy macierzy. To oczywiście marnotrawstwo, aby dodać całe matryce. matrix_add na ratunek:

struct matrix_add {
    // … yadda, yadda, yadda …

    int operator ()(unsigned int x, unsigned int y) {
        // Calculate *just one* element:
        return a(x, y) + b(x, y);
    }
};

Inne przykłady obfitują. Właśnie przypomniałem sobie, że niedawno wdrożyłem coś związanego. Zasadniczo musiałem zaimplementować klasę string, która powinna być zgodna ze stałym, predefiniowanym interfejsem. Jednak moja szczególna Klasa strun zajmowała się ogromnymi strunami to nie było zapisane w pamięci. Zwykle użytkownik uzyskuje dostęp do małych podłańcuchów z oryginalnego łańcucha za pomocą funkcji infix. Przeładowałem tę funkcję dla mojego typu string, aby zwrócić proxy, który zawierał odniesienie do mojego ciągu, wraz z żądaną pozycją początkową i końcową. Tylko wtedy, gdy ten podłańcuch był rzeczywiście używany, odpytywał API C, aby pobrać tę część łańcucha.

 78
Author: Konrad Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-22 18:14:44

Boost.Lambda jest bardzo ładna, ale [[2]]Proto jest Dokładnie tym, czego szukasz. Ma już przeciążenia wszystkich operatorów C++, które domyślnie wykonują swoją zwykłą funkcję po wywołaniu proto::eval(), ale można ją zmienić.

 29
Author: j_random_hacker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-08 06:55:16

To, co Konrad już wyjaśnił, można umieścić dalej, aby wspierać zagnieżdżone wywołania operatorów, wszystkie wykonywane leniwie. W przykładzie Konrada ma on obiekt wyrażenia, który może przechowywać dokładnie dwa argumenty, dla dokładnie dwóch operandów jednej operacji. Problem polega na tym, że wykonuje tylko JEDEN podwyrażenie leniwie, co ładnie wyjaśnia pojęcie w leniwej ocenie w prostych słowach, ale nie poprawia znacząco wydajności. Drugi przykład pokazuje również dobrze, jak można apply operator() to add only some elements using that expression object. Ale aby ocenić dowolne wyrażenia złożone, potrzebujemy jakiegoś mechanizmu, który może przechowywać strukturę tego też. Nie możemy obejść szablonów, aby to zrobić. A nazwa to expression templates. Idea jest taka, że jeden obiekt wyrażenia szablonowego może przechowywać strukturę niektórych arbitralnych podprefleksji rekurencyjnie, jak drzewo, gdzie operacje są węzłami, a operandami są węzłami potomnymi. Dla bardzo dobry Wyjaśnienie, które znalazłem dzisiaj (kilka dni po napisaniu poniższego kodu) zobacz tutaj .

template<typename Lhs, typename Rhs>
struct AddOp {
    Lhs const& lhs;
    Rhs const& rhs;

    AddOp(Lhs const& lhs, Rhs const& rhs):lhs(lhs), rhs(rhs) {
        // empty body
    }

    Lhs const& get_lhs() const { return lhs; }
    Rhs const& get_rhs() const { return rhs; }
};

Zapisuje dowolną operację dodawania, nawet zagnieżdżoną, co widać po następującej definicji operatora + dla prostego typu punktowego:

struct Point { int x, y; };

// add expression template with point at the right
template<typename Lhs, typename Rhs> AddOp<AddOp<Lhs, Rhs>, Point> 
operator+(AddOp<Lhs, Rhs> const& lhs, Point const& p) {
    return AddOp<AddOp<Lhs, Rhs>, Point>(lhs, p);
} 

// add expression template with point at the left
template<typename Lhs, typename Rhs> AddOp< Point, AddOp<Lhs, Rhs> > 
operator+(Point const& p, AddOp<Lhs, Rhs> const& rhs) {
    return AddOp< Point, AddOp<Lhs, Rhs> >(p, rhs);
}

// add two points, yield a expression template    
AddOp< Point, Point > 
operator+(Point const& lhs, Point const& rhs) {
    return AddOp<Point, Point>(lhs, rhs);
}

Teraz, jeśli masz

Point p1 = { 1, 2 }, p2 = { 3, 4 }, p3 = { 5, 6 };
p1 + (p2 + p3); // returns AddOp< Point, AddOp<Point, Point> >

Teraz wystarczy przeciążyć operator = i dodać odpowiedni konstruktor dla typu punktu i zaakceptować AddOp. Zmień jego definicję na:

struct Point { 
    int x, y; 

    Point(int x = 0, int y = 0):x(x), y(y) { }

    template<typename Lhs, typename Rhs>
    Point(AddOp<Lhs, Rhs> const& op) {
        x = op.get_x();
        y = op.get_y();
    }

    template<typename Lhs, typename Rhs>
    Point& operator=(AddOp<Lhs, Rhs> const& op) {
        x = op.get_x();
        y = op.get_y();
        return *this;
    }

    int get_x() const { return x; }
    int get_y() const { return y; }
};

I dodaj odpowiedni get_x i get_y to AddOp as member functions:

int get_x() const {
    return lhs.get_x() + rhs.get_x();
}

int get_y() const {
    return lhs.get_y() + rhs.get_y();
}

Zauważ, że nie stworzyliśmy żadnych tymczasowych elementów typu Point. Mogła to być duża matryca z wieloma polami. Ale w momencie, gdy wynik jest potrzebny, obliczamy go leniwie .

 23
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-20 14:58:38

Nie mam nic do dodania do postu Konrada, ale można spojrzeć na Eigen na przykład leniwej oceny wykonanej dobrze, w aplikacji realnego świata. To jest dość inspirujące.

 9
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-05 21:14:23

Myślę o zaimplementowaniu klasy szablonów, która używa std::function. Klasa powinna, mniej więcej, wyglądać tak:

template <typename Value>
class Lazy
{
public:
    Lazy(std::function<Value()> function) : _function(function), _evaluated(false) {}

    Value &operator*()  { Evaluate(); return  _value; }
    Value *operator->() { Evaluate(); return &_value; }

private:
    void Evaluate()
    {
        if (!_evaluated)
        {
            _value = _function();
            _evaluated = true;
        }
    }

    std::function<Value()> _function;
    Value _value;
    bool _evaluated;
};

Na przykład użycie:

class Noisy
{
public:
    Noisy(int i = 0) : _i(i)
    {
        std::cout << "Noisy(" << _i << ")"  << std::endl;
    }
    Noisy(const Noisy &that) : _i(that._i)
    {
        std::cout << "Noisy(const Noisy &)" << std::endl;
    }
    ~Noisy()
    {
        std::cout << "~Noisy(" << _i << ")" << std::endl;
    }

    void MakeNoise()
    {
        std::cout << "MakeNoise(" << _i << ")" << std::endl;
    }
private:
    int _i;
};  

int main()
{
    Lazy<Noisy> n = [] () { return Noisy(10); };

    std::cout << "about to make noise" << std::endl;

    n->MakeNoise();
    (*n).MakeNoise();
    auto &nn = *n;
    nn.MakeNoise();
}

Powyższy kod powinien wygenerować następujący komunikat na konsoli:

Noisy(0)
about to make noise
Noisy(10)
~Noisy(10)
MakeNoise(10)
MakeNoise(10)
MakeNoise(10)
~Noisy(10)

Zauważ, że konstruktor printing Noisy(10) nie zostanie wywołany dopóki zmienna nie będzie dostępna.

Ta klasa jest daleka od doskonałości. Pierwszą rzeczą będzie to, że domyślny konstruktor Value będzie musiał zostać wywołany na member inicjalizacja (drukowanie Noisy(0) w tym przypadku). Zamiast tego możemy użyć wskaźnika _value, ale nie jestem pewien, czy wpłynie to na wydajność.
 3
Author: hiapay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-18 10:19:50

Odpowiedź Johannesa działa.Ale jeśli chodzi o więcej nawiasów, to nie działa jak życzenie. Oto przykład.

Point p1 = { 1, 2 }, p2 = { 3, 4 }, p3 = { 5, 6 }, p4 = { 7, 8 };
(p1 + p2) + (p3+p4)// it works ,but not lazy enough

Ponieważ operator three overloaded + nie pokrył sprawy

AddOp<Llhs,Lrhs>+AddOp<Rlhs,Rrhs>

Więc kompilator musi przekonwertować albo (p1+p2) albo (p3 + p4) do punktu ,to nie jest wystarczająco leniwe.A kiedy kompilator decyduje, który konwertować, narzeka. Bo nikt nie jest lepszy od drugiego . Oto moje rozszerzenie: add yet another overloaded operator +

    template <typename LLhs, typename LRhs, typename RLhs, typename RRhs>
AddOp<AddOp<LLhs, LRhs>, AddOp<RLhs, RRhs>> operator+(const AddOp<LLhs, LRhs> & leftOperandconst, const AddOp<RLhs, RRhs> & rightOperand)
{
    return  AddOp<AddOp<LLhs, LRhs>, AddOp<RLhs, RRhs>>(leftOperandconst, rightOperand);

}

Teraz, kompilator poradzi sobie z powyższym przypadkiem poprawnie, bez konwersji implicit, volia!

 3
Author: spiritsaway,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-08 07:56:22

C++0x jest fajny i w ogóle.... ale dla tych z nas żyjących w teraźniejszości masz boost lambda library i Boost Phoenix. Oba z zamiarem wprowadzenia dużych ilości programowania funkcyjnego do C++.

 2
Author: Hippiehunter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-05 20:36:16

Wszystko jest możliwe.

To zależy dokładnie co masz na myśli:

class X
{
     public: static X& getObjectA()
     {
          static X instanceA;

          return instanceA;
     }
};

Tutaj mamy wpływ zmiennej globalnej, która jest leniwie oceniana w punkcie pierwszego użycia.

Zgodnie z nowym wnioskiem w pytaniu.
I kradzież projektu Konrada Rudolfa i jego rozbudowę.

Obiekt leniwy:

template<typename O,typename T1,typename T2>
struct Lazy
{
    Lazy(T1 const& l,T2 const& r)
        :lhs(l),rhs(r) {}

    typedef typename O::Result  Result;
    operator Result() const
    {
        O   op;
        return op(lhs,rhs);
    }
    private:
        T1 const&   lhs;
        T2 const&   rhs;
};

Jak go używać:

namespace M
{
    class Matrix
    {
    };
    struct MatrixAdd
    {
        typedef Matrix  Result;
        Result operator()(Matrix const& lhs,Matrix const& rhs) const
        {
            Result  r;
            return r;
        }
    };
    struct MatrixSub
    {
        typedef Matrix  Result;
        Result operator()(Matrix const& lhs,Matrix const& rhs) const
        {
            Result  r;
            return r;
        }
    };
    template<typename T1,typename T2>
    Lazy<MatrixAdd,T1,T2> operator+(T1 const& lhs,T2 const& rhs)
    {
        return Lazy<MatrixAdd,T1,T2>(lhs,rhs);
    }
    template<typename T1,typename T2>
    Lazy<MatrixSub,T1,T2> operator-(T1 const& lhs,T2 const& rhs)
    {
        return Lazy<MatrixSub,T1,T2>(lhs,rhs);
    }
}
 2
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-21 07:12:08

Jak to będzie zrobione w C++0x, za pomocą wyrażeń lambda.

 1
Author: Mehrdad Afshari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-05 19:53:45

W C++11 leniwa ocena podobna do odpowiedzi hiapay można uzyskać za pomocą STD:: shared_future. W lambdzie trzeba jeszcze zawęzić obliczenia, ale memoizacja jest załatwiona:

std::shared_future<int> a = std::async(std::launch::deferred, [](){ return 1+1; });

Oto pełny przykład:

#include <iostream>
#include <future>

#define LAZY(EXPR, ...) std::async(std::launch::deferred, [__VA_ARGS__](){ std::cout << "evaluating "#EXPR << std::endl; return EXPR; })

int main() {
    std::shared_future<int> f1 = LAZY(8);
    std::shared_future<int> f2 = LAZY(2);
    std::shared_future<int> f3 = LAZY(f1.get() * f2.get(), f1, f2);

    std::cout << "f3 = " << f3.get() << std::endl;
    std::cout << "f2 = " << f2.get() << std::endl;
    std::cout << "f1 = " << f1.get() << std::endl;
    return 0;
}
 1
Author: user2259659,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-01 21:01:38

Weźmy Haskella za naszą inspirację-jest leniwy do głębi. Pamiętajmy również, jak Linq w C# używa Enumeratorów w sposób monadyczny (urgh - tu jest słowo - sorry). Na koniec pamiętajmy o tym, co coroutines mają dostarczać programistom. Mianowicie oddzielenie od siebie kroków obliczeniowych (np. producenta konsumenta). I spróbujmy pomyśleć o tym, jak coroutines odnoszą się do leniwej oceny.

Wszystkie powyższe wydaje się być jakoś powiązane.

Następnie spróbujmy wyodrębnić naszą osobistą definicję tego, do czego sprowadza się "lenistwo".

Jedna z interpretacji brzmi: chcemy przedstawić nasze obliczenia w sposób złożony, przed wykonaniem go. Niektóre z tych części, których używamy do komponowania naszego kompletnego rozwiązania, mogą bardzo dobrze czerpać z ogromnych (czasami nieskończonych) źródeł danych, a nasze pełne obliczenia również generują skończony lub nieskończony wynik.

Lets get concrete and into some code. Potrzebujemy przykładu za to! Tutaj wybieram "problem" fizzbuzz jako przykład, tylko dlatego, że istnieje jakieś ładne, leniwe rozwiązanie.

W Haskell wygląda to tak:

module FizzBuzz
( fb
)
where
fb n =
    fmap merge fizzBuzzAndNumbers
    where
        fizz = cycle ["","","fizz"]
        buzz = cycle ["","","","","buzz"]
        fizzBuzz = zipWith (++) fizz buzz
        fizzBuzzAndNumbers = zip [1..n] fizzBuzz
        merge (x,s) = if length s == 0 then show x else s

Funkcja Haskella cycle tworzy nieskończoną listę (leniwa, oczywiście!) z skończonej listy, po prostu powtarzając wartości na skończonej liście w nieskończoność. W gorliwym stylu programowania pisanie czegoś takiego zabrzmiało alarmowo (przepełnienie pamięci, nieskończone pętle!). Ale nie tak w leniwym języku. Sztuczka polega na tym, że leniwe listy nie są obliczane od razu. Może nigdy. Zwykle tylko tyle, ile wymaga tego kolejny kod.

Trzecia linia w bloku where powyżej tworzy kolejny lazy!! lista, poprzez połączenie nieskończonych list fizz i buzz za pomocą przepisu single two elements "concatenate a string element from either input list into a single string". Ponownie, gdyby to miało zostać natychmiast ocenione, musielibyśmy czekać, aż nasz komputer skończy się zasoby.

W czwartej linii tworzymy krotki członków skończonej listy leniwej [1..n] z naszą nieskończoną listą leniwej fizzbuzz. Wynik jest nadal leniwy.

Nawet w głównej części naszej funkcji fb, nie ma potrzeby, aby być chętnym. Cała funkcja zwraca listę z rozwiązaniem, które samo w sobie jest-znowu - leniwe. Równie dobrze możesz myśleć o wyniku fb 50 jako o obliczeniach, które możesz (częściowo) ocenić później. Lub połączyć z innymi rzeczami, co prowadzi do jeszcze większego (leniwy) ocena.

Tak więc, aby rozpocząć z naszą wersją C++ "fizzbuzz", musimy pomyśleć o sposobach łączenia częściowych kroków naszych obliczeń w większe bity obliczeń, z których każdy rysuje dane z poprzednich kroków zgodnie z wymaganiami.

[[36]}możesz zobaczyć pełną historię w a gist of mine .

Oto podstawowe idee stojące za kodem:

W C # i Linq tworzymy stateful, generic type Enumerator, który posiada
- Prąd wartość częściowego obliczenia
- Stan częściowego obliczenia (dzięki czemu możemy produkować kolejne wartości)
- Funkcja worker, która tworzy Następny stan, następną wartość i bool, który stwierdza, czy jest więcej danych lub czy wyliczenie zostało zakończone.

Aby móc komponować instancję Enumerator<T,S> za pomocą mocy . (kropka), klasa ta zawiera również funkcje, zapożyczone z klas typu Haskell, takich jak Functor i Applicative.

Funkcja worker dla enumeratora ma zawsze postać: S -> std::tuple<bool,S,T gdzie {[21] } jest zmienną typu ogólnego reprezentującą stan, a {[22] } jest zmienną typu ogólnego reprezentującą wartość-wynik kroku obliczeń.

Wszystko to jest już widoczne w pierwszych liniach definicji klasy Enumerator.

template <class T, class S>
class Enumerator
{
public:
    typedef typename S State_t;
    typedef typename T Value_t;
    typedef std::function<
        std::tuple<bool, State_t, Value_t>
        (const State_t&
            )
    > Worker_t;

    Enumerator(Worker_t worker, State_t s0)
        : m_worker(worker)
        , m_state(s0)
        , m_value{}
    {
    }
    // ...
};

Więc, wszystko, czego potrzebujemy, aby utworzyć konkretną instancję enumeratora, musimy stworzyć funkcję workera, mieć stan początkowy i utworzyć przykład Enumerator z tymi dwoma argumentami.

Tutaj przykład-funkcja range(first,last) tworzy skończony zakres wartości. Odpowiada to leniwej liście w świecie Haskell.

template <class T>
Enumerator<T, T> range(const T& first, const T& last)
{
    auto finiteRange =
        [first, last](const T& state)
    {
        T v = state;
        T s1 = (state < last) ? (state + 1) : state;
        bool active = state != s1;
        return std::make_tuple(active, s1, v);
    };
    return Enumerator<T,T>(finiteRange, first);
}

I możemy skorzystać z tej funkcji, na przykład tak: auto r1 = range(size_t{1},10); - stworzyliśmy sobie leniwą listę z 10 elementami!

Teraz brakuje nam tylko doświadczenia "wow", aby zobaczyć, jak możemy komponować enumeratory. Wracając do funkcji Haskells cycle, która jest całkiem fajna. Jak czy wyglądałoby to w naszym świecie C++? Oto ona:

template <class T, class S>
auto
cycle
( Enumerator<T, S> values
) -> Enumerator<T, S>
{
    auto eternally =
        [values](const S& state) -> std::tuple<bool, S, T>
    {
        auto[active, s1, v] = values.step(state);
        if (active)
        {
            return std::make_tuple(active, s1, v);
        }
        else
        {
            return std::make_tuple(true, values.state(), v);
        }
    };
    return Enumerator<T, S>(eternally, values.state());
}

Pobiera enumerator jako dane wejściowe i zwraca enumerator. Local (lambda) function eternally po prostu resetuje liczbę wejściową do jej wartości początkowej, gdy zabraknie jej wartości i voilà-mamy nieskończoną, powtarzającą się wersję listy, którą podaliśmy jako argument:: auto foo = cycle(range(size_t{1},3)); i już możemy bezwstydnie komponować nasze leniwe "obliczenia".

zip jest dobrym przykładem, pokazującym, że możemy również utworzyć nowy enumerator z dwa enumeratory wejściowe. Wynikowy enumerator daje tyle wartości, co mniejszy z jednego z enumeratorów wejściowych (krotki z 2 elementami, po jednej dla każdego enumeratora wejściowego). Zaimplementowałem zip wewnątrz class Enumerator samego siebie. Oto jak to wygląda:

// member function of class Enumerator<S,T> 
template <class T1, class S1>
auto
zip
( Enumerator<T1, S1> other
) -> Enumerator<std::tuple<T, T1>, std::tuple<S, S1> >
{
    auto worker0 = this->m_worker;
    auto worker1 = other.worker();
    auto combine =
        [worker0,worker1](std::tuple<S, S1> state) ->
        std::tuple<bool, std::tuple<S, S1>, std::tuple<T, T1> >
    {
        auto[s0, s1] = state;
        auto[active0, newS0, v0] = worker0(s0);
        auto[active1, newS1, v1] = worker1(s1);
        return std::make_tuple
            ( active0 && active1
            , std::make_tuple(newS0, newS1)
            , std::make_tuple(v0, v1)
            );
    };
    return Enumerator<std::tuple<T, T1>, std::tuple<S, S1> >
        ( combine
        , std::make_tuple(m_state, other.state())
        );
}

Proszę zauważyć, że "łączenie" również kończy się połączeniem stanu obu źródeł i wartości obu źródeł.

Ponieważ ten post jest już TL; DR; dla wielu, tutaj the...

Podsumowanie

Tak, leniwa ewaluacja może być zaimplementowana w C++. Tutaj, zrobiłem to zapożyczając nazwy funkcji z haskell i paradygmat z C# enumerators i Linq. Mogą być podobieństwa do pythons itertools, btw. Myślę, że zastosowali podobne podejście.

Moja implementacja (patrz link gist powyżej) to tylko prototyp-Nie kod produkcyjny, btw. Więc żadnych gwarancji z mojej strony. Służy również jako kod demo, aby uzyskać ogólne pomysł w poprzek.

A jaka byłaby ta odpowiedź bez ostatecznej wersji C++ fizzbuz, eh? Oto ona:

std::string fizzbuzz(size_t n)
{
    typedef std::vector<std::string> SVec;
    // merge (x,s) = if length s == 0 then show x else s
    auto merge =
        [](const std::tuple<size_t, std::string> & value)
        -> std::string
    {
        auto[x, s] = value;
        if (s.length() > 0) return s; 
        else return std::to_string(x);
    };

    SVec fizzes{ "","","fizz" };
    SVec buzzes{ "","","","","buzz" };

    return
    range(size_t{ 1 }, n)
    .zip
        ( cycle(iterRange(fizzes.cbegin(), fizzes.cend()))
          .zipWith
            ( std::function(concatStrings)
            , cycle(iterRange(buzzes.cbegin(), buzzes.cend()))
            )
        )
    .map<std::string>(merge)
    .statefulFold<std::ostringstream&>
    (
        [](std::ostringstream& oss, const std::string& s) 
        {
            if (0 == oss.tellp())
            {
                oss << s;
            }
            else
            {
                oss << "," << s;
            }
        }
        , std::ostringstream()
    )
    .str();
}

I... fizzbuzz, który zwraca" nieskończoną listę " do rozmówcy: {]}

typedef std::vector<std::string> SVec;
static const SVec fizzes{ "","","fizz" };
static const SVec buzzes{ "","","","","buzz" };

auto fizzbuzzInfinite() -> decltype(auto)
{
    // merge (x,s) = if length s == 0 then show x else s
    auto merge =
        [](const std::tuple<size_t, std::string> & value)
        -> std::string
    {
        auto[x, s] = value;
        if (s.length() > 0) return s;
        else return std::to_string(x);
    };

    auto result =
        range(size_t{ 1 })
        .zip
        (cycle(iterRange(fizzes.cbegin(), fizzes.cend()))
            .zipWith
            (std::function(concatStrings)
                , cycle(iterRange(buzzes.cbegin(), buzzes.cend()))
            )
        )
        .map<std::string>(merge)
        ;
    return result;
}

Warto pokazać, ponieważ można się z niego dowiedzieć, jak uniknąć pytania, Jaki jest dokładny typ zwracania tej funkcji (ponieważ zależy to od implementacji samej funkcji, a mianowicie od tego, jak kod łączy enumeratorów).

Pokazuje również, że musieliśmy przenieść wektory fizzes i buzzes poza zakres funkcji, więc są one nadal wokół, gdy ostatecznie Na Zewnątrz, leniwy mechanizm wytwarza wartości. Gdybyśmy tego nie zrobili, kod iterRange(..) przechowywałby Iteratory do wektorów, które już dawno odeszły.

 0
Author: BitTickler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-21 22:21:33

Łatwo jest stworzyć własną klasę "container", która pobiera obiekt funkcji generującej i eksponuje Iteratory.

 -2
Author: Eclipse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-05 19:44:24