Frage

C ++ nicht über native Unterstützung für lazy evaluation (wie Haskell tut).

Ich frage mich, ob es möglich ist faul Auswertung in C ++ in einer angemessenen Art und Weise zu implementieren. Wenn ja, wie würden Sie es tun?

EDIT: Ich mag Konrad Rudolph Antwort

.

Ich frage mich, ob es möglich ist, es in einer allgemeinere Art und Weise zu implementieren, beispielsweise durch eine parametrisierte Klasse mit faul, dass im Wesentlichen für T funktioniert die Art und Weise matrix_add für Matrix funktioniert.

Jede Operation auf T zurückkehren würde, anstatt faul. Das einzige Problem besteht darin, die Argumente und den Betrieb Code innerhalb faul selbst zu speichern. Kann jemand sehen, wie diese zu verbessern?

War es hilfreich?

Lösung

  

Ich frage mich, ob es möglich ist faul Auswertung in C ++ in einer angemessenen Art und Weise zu implementieren. Wenn ja, wie würden Sie es tun?

Ja, das ist möglich und sehr oft getan, z.B. für Matrix-Berechnungen. Der Hauptmechanismus, dies zu erleichtern, ist Betreiber Überlastung. Betrachten wir den Fall Matrix hinaus. Die Signatur der Funktion würde in der Regel wie folgt aussehen:

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

Jetzt, damit diese Funktion faul, es ist genug, um einen Proxy anstelle des tatsächlichen Ergebnisses zurück:

struct matrix_add;

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

Jetzt alles, was getan werden muss, ist diese Proxy zu schreiben:

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

Die Magie liegt in dem Verfahren, das ein impliziter operator matrix() Umwandlungsoperator von matrix_add auf Normal matrix ist. Auf diese Weise können Sie die Kette mehrere Operationen (durch entsprechende Überlastungen natürlich Providing). Die Auswertung erfolgt nur dann, wenn das endgültige Ergebnis zu einer matrix Instanz zugeordnet ist.

Bearbeiten Ich habe noch deutlicher gewesen. Wie es ist, macht der Code keinen Sinn, denn obwohl Auswertung träge geschieht, immer noch in dem gleichen Ausdruck geschieht. Insbesondere wird eine weitere Ergänzung diesen Code bewerten, es sei denn die matrix_add Struktur zu ermöglichen gekettet zusätzlich geändert wird. C ++ 0x erleichtert dies erheblich, indem variadische Vorlagen (das heißt Vorlage Listen mit variabler Länge).

Allerdings ist ein sehr einfacher Fall, dass dieser Code einen echten, direkten Nutzen tatsächlich haben würde, ist die folgende:

int value = (A + B)(2, 3);

Hier wird angenommen, dass A und B sind zweidimensionale Matrizen und Dereferenzieren ist in Fortran Notation gemacht, das heißt, die oben berechnet ein Element aus einer Matrix Summe. Es ist natürlich auch verschwenderisch die ganzen Matrizen hinzuzufügen. matrix_add zur Rettung:

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

Weitere Beispiele gibt es zuhauf. Ich habe gerade daran erinnert, dass ich etwas im Zusammenhang vor nicht langer Zeit umgesetzt haben. Im Grunde hatte ich eine String-Klasse zu implementieren, die auf eine feste, vordefinierte Schnittstelle haften sollen. Allerdings behandeln meine besondere String-Klasse mit riesigen Zeichenfolge, die im Speicher nicht tatsächlich gespeichert wurden. Normalerweise würde der Benutzer nur Zugriff auf kleinen Teil von der ursprünglichen Zeichenfolge mit einer Funktion infix. I überlastete diese Funktion für meinen String-Typen einen Proxy zurückzugeben, die einen Verweis auf meine Saite gehalten wird, zusammen mit der gewünschten Start- und Endposition. Erst wenn wurde dieser Teilkette tatsächlich verwendet hat es eine C-API abfragen, diesen Teil der Zeichenfolge abgerufen werden.

Andere Tipps

Boost.Lambda ist sehr schön, aber Boost.Proto ist genau , was Sie suchen. Es hat bereits Überlastungen von alle C ++ Operatoren, die standardmäßig ihre übliche Funktion ausführen, wenn proto::eval() genannt wird, kann aber geändert werden.

Was Konrad bereits erklärt weiter umgesetzt werden kann verschachtelte Anrufungen von Operatoren, alle träge ausgeführt zu unterstützen. In Konrads Beispiel hat er ein Ausdruck Objekt, das genau zwei Argumente speichern kann, für genau zwei Operanden einer Operation. Das Problem ist, dass es nur dann ausgeführt wird ein subexpression träge, die gut das Konzept in lazy evaluation in einfachen Worten erklärt setzen, aber die Leistung nicht wesentlich verbessern. Das andere Beispiel zeigt gut auch, wie man operator() anwenden können nur einige Elemente Objekt verwenden diesen Ausdruck hinzuzufügen. Aber zu bewerten beliebige komplexe Ausdrücke, brauchen wir einen Mechanismus, die können speichern die Struktur das auch. Wir können nicht Vorlagen umgehen, das zu tun. Und der Name für das heißt expression templates. Die Idee ist, dass ein Templat-Ausdruck Objekt rekursiv, die die Struktur eines beliebigen Unterausdruckes speichern kann, wie ein Baum, wo die Operationen die Knoten sind, und die Operanden sind der Kind-Knoten. Für eine sehr gute Erklärung fand ich heute nur (einige Tage, nachdem ich den Code unten geschrieben) finden Sie unter hier .

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

Das speichert jede Additionsoperation, auch verschachtelte ein, wie durch die folgende Definition eines Operators + für einen einfachen Punkttyp zu sehen:

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

Nun, wenn Sie haben

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

Sie müssen jetzt nur Betreiber zu überlasten = und einen geeigneten Konstruktor für den Punkttyp hinzufügen und akzeptieren AddOp. Ändern Sie die Definition an:

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

Und fügen Sie den entsprechenden get_x und get_y in AddOp als Member-Funktionen:

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

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

Beachten Sie, wie wir keine Provisorien vom Typ Point erstellt haben. Es könnte eine große Matrix mit vielen Feldern haben. Aber zu der Zeit das Ergebnis benötigt wird, berechnen wir es lazily .

Ich habe nichts zu Konrads Beitrag hinzuzufügen, aber Sie können sehen Eigen ein Beispiel für lazy evaluation richtig gemacht, in einem realen Welt-App. Es ist ziemlich atemberaubend.

Ich denke über eine Template-Klasse Implementierung, die std::function verwendet. Die Klasse sollte mehr oder weniger, wie folgt aussehen:

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

Zum Beispiel Nutzung:

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

über Code soll die folgende Meldung auf der Konsole erzeugen:

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

Beachten Sie, dass der Konstruktor Druck Noisy(10) nicht, bis die Variable zugegriffen wird, aufgerufen werden.

Diese Klasse ist bei weitem nicht perfekt, aber. Das erste, was wäre der Standardkonstruktor von Value auf Memberinitialisierungsliste genannt werden muss (Druck Noisy(0) in diesem Fall). Wir können stattdessen Zeiger für _value verwenden, aber ich bin mir nicht sicher, ob sie die Leistung beeinträchtigen würde.

Johannes' Antwort works.But, wenn es um mehr Klammern kommt, ist es nicht als Wunsch arbeiten. Hier ist ein Beispiel.

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

Da der drei überladene Operator + hat den Fall nicht abdecken

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

So ist der Compiler konvertiert hat entweder (p1 + p2) oder (p3 + p4) zu Punkt, das ist nicht faul enough.And wenn Compiler, der zu konvertieren, entscheidet sie beschwert. Da keiner ist besser als die andere. Hier kommt meine Erweiterung: fügen noch eine weitere überladenen 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);

}

Nun kann der Compiler den Fall oben richtig handhaben, und keine implizite Konvertierung, volia!

C ++ 0x ist schön und alle .... aber für diejenigen von uns in der Gegenwart haben Sie Boost-Lambda-Bibliothek leben und Boost Phoenix. Sowohl mit der Absicht zu bringen große Mengen an funktionaler Programmierung in C ++.

Alles ist möglich.

Es hängt davon ab, genau das, was Sie bedeuten:

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

          return instanceA;
     }
};

Hier haben wir die von einer globalen Variablen beeinflussen die träge an der Stelle der ersten Verwendung ausgewertet wird.

Als neu in der Frage gebeten.
Und Konrad Rudolph Design zu stehlen und zu ihrer Ausdehnung.

The Lazy Objekt:

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

Wie man es verwendet:

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

Wie es geht getan werden in C ++ 0x durch Lambda-Ausdrücke.

In C ++ 11 lazy evaluation ähnlich wie hiapay Antwort kann mit std :: shared_future erreicht werden. Sie haben noch Berechnungen in lambdas kapseln aber memoization ist gesorgt:

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

Hier ist ein vollständiges Beispiel:

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

Lets Haskell als unsere Inspiration nehmen - es faul zum Kern. Außerdem wollen sie im Auge behalten, wie Linq in C # verwendet Enumeratoren in einem monadischen (urgh - hier ist das Wort - sorry) Art und Weise. Last not least, können im Auge behalten, was Koroutinen sollen Programmierer zur Verfügung zu stellen. Nämlich die Entkopplung der Rechenschritte (z.B. Erzeuger Verbraucher) voneinander. Und läßt versuchen, darüber nachzudenken, wie Koroutinen zu faul Auswertung beziehen.

Alle oben genannten scheint irgendwie zusammenzuhängen.

Als nächstes kann versuchen, unsere persönliche Definition von zu extrahieren, was „faul“ ankommt.

Eine Interpretation ist: Wir wollen unsere Berechnung in einer zusammensetzbaren Weise erklären, vor Ausführen es. Einige dieser Teile verwenden wir unsere Komplettlösung komponieren könnten sehr gut stützen sich auf riesige (manchmal unendlich) Datenquellen, mit unserer vollen Berechnung entweder auch ein endliches oder unendliches Ergebnis.

Lets Beton erhalten und in einigen Code. Wir brauchen ein Beispiel dafür! Hier habe ich das fizzbuzz „Problem“ als Beispiel wählen, nur aus dem Grund, dass es einige schön, faul Lösung zu.

In Haskell, sieht es wie folgt aus:     

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

Die Haskell Funktion cycle schafft eine unendliche Liste (faul, natürlich!) Aus einer endlichen Liste, indem Sie einfach immer die Werte in der endlichen Liste wiederholen. In einem eifrigen Programmierstil, wäre so etwas wie das Schreiben läutet die Alarmglocken (Speicherüberlauf, Endlosschleifen!). Aber nicht so in einer faulen Sprache. Der Trick ist, dass faule Listen nicht sofort berechnet. Vielleicht nie. Normalerweise wird nur so viel wie nachfolgender Code erfordert es.

Die dritte Zeile in dem where Block oben schafft ein anderes faul !! Liste durch die unendlichen Listen fizz und buzz mittels des einzigen zwei Elemente Rezepts Kombination „ein Zeichenfolge Element entweder aus Eingangsliste in ein einzelnes String verketten“. Noch einmal, wenn dies sofort ausgewertet werden, würden wir müssen warten, für unsere Computer aus Ressourcen auszuführen.

In der 4. Zeile erstellen wir Tupel der Mitglieder einer endlichen faul Liste [1..n] mit unserer unendlich faul Liste fizzbuzz. Das Ergebnis ist immer noch faul.

Auch in dem Hauptkörper unserer fb Funktion, gibt es keine Notwendigkeit, eifrig zu bekommen. Die gesamte Funktion gibt eine Liste mit der Lösung, die sich -again- faul ist. Sie könnten als denken auch über das Ergebnis der fb 50 als Berechnung, die Sie (teilweise) später auszuwerten. Oder mit anderen Sachen kombinieren, was zu einer noch größeren (faul) Auswertung.

Also, um mit unserer C ++ Version von „fizzbuzz“ zu beginnen, müssen wir Möglichkeiten denken, wie in größeren Bits von Berechnungen, jeder Zeichnungsdaten aus vorherigen Schritten Teilschritte unserer Berechnung zu kombinieren, je nach Bedarf.

Sie können die ganze Geschichte sehen in ein Kern von mir .

Hier werden die grundlegenden Ideen hinter dem Code:

Ausleihe von C # und Linq, wir "erfinden" eine Stateful, generischer Typ Enumerator, die hält
  - Der aktuelle Wert der Teilberechnung
  - Der Zustand einer Teilberechnung (so können wir nachfolgende Werte erzeugen)
  - Die Arbeiter-Funktion, die den nächsten Zustand erzeugt, der nächste Wert und ein bool, die besagt, ob es mehr Daten sind, oder wenn die Aufzählung zu einem Ende gekommen ist.

Um in der Lage sein Enumerator<T,S> Instanz zu komponieren mittels der Kraft des . (dot), diese Klasse auch Funktionen enthält, entlehnt aus Haskell Typklassen wie Functor und Applicative.

Der Arbeiter-Funktion für enumerator ist immer von der Form: S -> std::tuple<bool,S,T wo S ist der generische Typ Variable, die den Zustand und T ist der generische Typ Variable einen Wert darstellt - das Ergebnis einer computation Schritt.

All dies ist bereits sichtbar in den ersten Zeilen der Enumerator Klassendefinition.

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

Also, alles, was wir eine bestimmte enumerator Instanz erstellen müssen, benötigen wir eine Arbeiter-Funktion zu erstellen, den Anfangszustand hat und eine Instanz von Enumerator mit diesen beiden Argumenten erstellen.

Hier ist eine Beispiel - Funktion range(first,last) schafft einen endlichen Wertebereich. Dies entspricht eine faule Liste in der Haskell Welt.

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

Und wir die Verwendung dieser Funktion, zum Beispiel so machen können: auto r1 = range(size_t{1},10); - Wir haben sie eine faule Liste erstellt mit 10 Elementen

Nun, alles ist für unsere „wow“ Erfahrung fehlt, ist zu sehen, wie wir Enumeratoren komponieren können. Kommen wir zurück zu Haskells cycle Funktion, die irgendwie cool ist. Wie wäre es in unserer C ++ Welt aussehen? Hier ist sie:

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

Es dauert einen Enumerator als Eingabe und gibt einen Enumerator. Local (Lambda) Funktion eternally einfach setzt die Eingangs Aufzählung auf seinen Startwert, wenn es aus Werten und voilà läuft - wir haben eine unendliche, immer wiederkehrende Version der Liste, die wir als Argument :: auto foo = cycle(range(size_t{1},3)); gab Und wir können schon schamlos unsere komponieren lazy "Berechnungen".

zip ist ein gutes Beispiel, was zeigt, dass wir auch einen neuen Enumerator aus zwei Eingängen Aufzählungen erstellen. Die sich ergebende Enumerator ergibt so viele Werte wie die kleinere von entweder der Eingangs Aufzählungen (Tupeln mit 2 Element, eine für jeden Eingang Enumerator). Ich habe zip innerhalb class Enumerator selbst implementiert. Hier ist, wie es aussieht:

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

Bitte beachten Sie, wie sich die „Kombination“ auch in Kombination den Zustand der beiden Quellen und die Werte beider Quellen endet.

Da dieser Beitrag ist bereits TL; DR; für viele, hier die ...

Zusammenfassung

Ja, faul Auswertung kann in C ++ implementiert werden. Hier, ich habe es durch Kreditaufnahme der Funktionsnamen von Haskell und das Paradigma von C # Aufzählungen und Linq. Es könnte Ähnlichkeiten zu Pythons itertools sein, btw. Ich denke, dass sie einen ähnlichen Ansatz verfolgt wird.

Meine Implementierung (siehe den Kern Link oben) ist nur ein Prototyp - nicht Produktionscode, btw. Also keine Gewährleistung von meiner Seite. Es dient auch als Demo-Code über die allgemeine Vorstellung davon zu bekommen, though.

Und was wäre diese Antwort ohne die endgültige C ++ Version von fizzbuz, eh? Hier ist sie:

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

Und ... den Punkt nach Hause noch weiter zu fahren - hier eine Variante von fizzbuzz, die eine „unendliche Liste“ an den Aufrufer zurückgibt:

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

Es lohnt sich zeigt, da man daraus lernen kann, wie man die Frage ausweichen, was die genaue Rückgabetyp dieser Funktion ist (wie es allein auf der Implementierung der Funktion abhängig ist, nämlich, wie der Code die Enumeratoren kombiniert).

Auch zeigt es, dass wir so sind sie immer noch um hatten, um die Vektoren fizzes und buzzes außerhalb des Bereichs der Funktion zu bewegen, wenn schließlich auf der Außenseite, der faule Mechanismus Werte erzeugt. Wenn wir das nicht getan hätte, wäre der iterRange(..) Code Iteratoren auf die Vektoren gespeichert, die lange verschwunden sind.

eine sehr einfache Definition von lazy evaluation Verwendung, die der Wert nicht, bis sie benötigt ausgewertet, würde ich sagen, dass man dies durch die Verwendung eines Zeigers und Makros (für Syntax Zucker) implementieren kann.

#include <stdatomic.h>

#define lazy(var_type) lazy_ ## var_type

#define def_lazy_type( var_type ) \
    typedef _Atomic var_type _atomic_ ## var_type; \
    typedef _atomic_ ## var_type * lazy(var_type);  //pointer to atomic type

#define def_lazy_variable(var_type, var_name ) \
    _atomic_ ## var_type _ ## var_name; \
    lazy_ ## var_type var_name = & _ ## var_name;

#define assign_lazy( var_name, val ) atomic_store( & _ ## var_name, val )
#define eval_lazy(var_name) atomic_load( &(*var_name) )

#include <stdio.h>

def_lazy_type(int)

void print_power2 ( lazy(int) i )
{
      printf( "%d\n", eval_lazy(i) * eval_lazy(i) );
}

typedef struct {
    int a;
} simple;

def_lazy_type(simple)

void print_simple ( lazy(simple) s )
{
    simple temp = eval_lazy(s);
    printf("%d\n", temp.a );
}


#define def_lazy_array1( var_type, nElements, var_name ) \
    _atomic_ ## var_type  _ ## var_name [ nElements ]; \
    lazy(var_type) var_name = _ ## var_name; 

int main ( )
{
    //declarations
    def_lazy_variable( int, X )
    def_lazy_variable( simple, Y)
    def_lazy_array1(int,10,Z)
    simple new_simple;

    //first the lazy int
    assign_lazy(X,111);
    print_power2(X);

    //second the lazy struct
    new_simple.a = 555;
    assign_lazy(Y,new_simple);
    print_simple ( Y );

    //third the array of lazy ints
    for(int i=0; i < 10; i++)
    {
        assign_lazy( Z[i], i );
    }

    for(int i=0; i < 10; i++)
    {
        int r = eval_lazy( &Z[i] ); //must pass with &
        printf("%d\n", r );
    }

    return 0;
}

Sie werden in der Funktion print_power2 bemerken, dass es ein Makro namens eval_lazy die nichts anderes tut, ist als dereferenzieren einen Zeiger um den Wert zu erhalten, kurz vor, wenn es tatsächlich benötigt wird. Der faule Typ ist atomar zugegriffen wird, so ist es vollständig Thread-sicher.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top