Funktionale Spielereien



  • Ja, das geht. Ich hätte aber gerne so eine funktionale Variante, mit der ich auch

    double t = twice(twice)(sqrt)(65536); // 4-facher sqrt-Aufruf
    

    bzw. was Äquivalents schreiben kann. Oder auch sowas:

    double t = twice(twice)(twice)(sqrt)(65536); // 16-facher sqrt-Aufruf
    

    Möglicherweise müsste man noch mehr Klammern setzen, ka, ob C++ sowas zulässt, aber wenn man den Term von links nach recht auswertet, ist es eindeutig, was gemeint ist.

    @cooky: ja, das ist auch ganz nice. Aber ich möchte ineinander schachteln können. Ich brauche also eine Funktion, die eine Funktion auf eine andere Funktion abbildet. Lesbarkeit ist nicht so wichtig, es geht hier nur ums Ausprobieren.



  • So könnte man anfangen:

    template<class G, class F>
    auto twice(F f) -> std::function<typename std::result_of<F(G)>::type(G)> {return [=](G x){return f(f(x));};}
    
    auto mysqrt=[](double v) {return std::sqrt(v);}; //std::sqrt ist mehrfach überladen
    std::cout << twice<double>(mysqrt)(65536) << std::endl;
    

    Die explizite Angabe des Parametertyps müsste sich auch noch wegzaubern lassen.



  • Was für einen Parametertyp müsste ich denn angeben, wenn ich "twice<...>(twice<double>)(mysqrt)(65536)" auswerten möchte?



  • Wurde das nicht schon alles mal gemacht, also in einer Bibliothek der Kommaoperator ueberladen um Verkettung a la Haskell zu erreichen?



  • Ramanujan schrieb:

    Was für einen Parametertyp müsste ich denn angeben, wenn ich "twice<...>(twice<double>)(mysqrt)(65536)" auswerten möchte?

    Anzugeben ist jeweils der Parametertyp, den die erzeugte Funktion entgegennehmen soll, also idealerweise:

    twice<double>(twice<double>(mysqrt))(65536)
    

    Darf aber auch ein anderer Typ sein, der sich implizit nach double konvertieren lässt, z.B. int.



  • Ich möchte aber nicht

    twice<double>(twice<double>(mysqrt))(65536)
    

    sondern

    (twice<double>(twice<double>))(mysqrt)(65536)
    

    die erzeuge Funktion von (dem ersten) twice nimmt als Typ eine Funktion von double nach double entgegen. Wie gebe ich das nun als Parameter an?

    (Bei diesem Beispiel sind beide Ausdrücke äquivalent, bei 3 twice ist das aber nicht mehr der Fall)



  • Ich denke nicht, dass das möglich ist. Der Ausdruck (twice<>()) müsste irgendeinen Typen haben, kann er aber nicht, weil man ja alles Aufrufbare benutzen können soll. Man könnte sich uU auf Funktionspointer beschränken, aber wozu dann überhaupt diese Syntax, wenn man nicht "wirklich" funktional programmieren kann? Abgesehen davon finde ich meine Variante von oben eh hübscher, und als Funktions-Objekt kann es auch herhalten.



  • cooky451 schrieb:

    Abgesehen davon finde ich meine Variante von oben eh hübscher, und als Funktions-Objekt kann es auch herhalten.

    Sehe ich nicht so, dein Ding ist absolut unhandlich. Hier ein besserer Ansatz:

    template <typename F>
    struct twice_ {
      twice_(F f) : f(std::move(f)) {}
      F f;
      template <typename T, typename R = decltype(f(f(std::declval<T>())))>
      R operator () (T x) { return f(f(x)); }
    };
    template <typename F> twice_<F> twice(F f) { return twice_<F>(std::move(f)); }
    
    int main()
    {
      auto square = [](double x){return x*x;};
      std::transform(std::istream_iterator<double>(std::cin),
                     std::istream_iterator<double>{},
                     std::ostream_iterator<double>(std::cout, "\n"),
                     twice(twice(square))); // 4-facher square-Aufruf, hoch 16               
    }
    

    Wie würde Zeile 16 mit deinem Ansatz lauten?



  • Ja, aber auch bei deiner Lösung kann man

    twice(twice)(square)(2)
    

    nicht schreiben. Ich denke mal, dass man sowas auch nicht mit C++ machen kann. Man bedenke, dass das linke twice mathematisch gesehen vom Typ
    ((d -> d) -> (d -> d)) -> ((d -> d) -> (d -> d))
    ist, wobei d für double steht. Bei 3 twice verdoppelt sich nochmal die Länge des Typs.

    Es ging hier übrigens nicht um eine hübsche Lösung, sondern um die Machbarkeit allgemein.



  • Das Problem ist, dass twice auf die Art keine Funktion ist, sondern eine Funktionsvorlage -- insbesondere, dass twice kein Objekt ist. Das lässt sich umgehen, aber mit Lambdas kommt man da (noch?) nicht weit.

    Ich stelle mir das grundsätzlich etwa so vor:

    #include <cmath>
    #include <iostream>
    
    template<typename F>
    struct apply_twice_type {
      F func_;
    
      apply_twice_type(F f) : func_(f) { }
    
      template<typename P>
      auto operator()(P p) const -> decltype(func_(func_(p))) { return func_(func_(p)); }
    };
    
    struct twice_type {
      template<typename F>
      apply_twice_type<F> operator()(F f) const { return apply_twice_type<F>(f); }
    } const twice; // <-- twice jetzt ein Objekt
    
    int main() {
      double (*sq)(double) = std::sqrt; // Überladene Funktion, Auflösung notwendig.
      std::cout << twice(twice)(sq)(16.0) << '\n';
    }
    

    Es gibt Vorschläge für den nächsten Standard, die Lambdas mit generischem operator() vorsehen, aber das ist wohl noch eine Weile hin. Wenn das kommt, dann könnte man diesen Mechanismus mit Lambdas auch nachbauen.



  • Cool, es geht ja doch. Thx.

    Traumhaft, wie lange es dauert, das hier zu kompilieren:

    int main() { 
       auto succ = [](int x){return x+1;};
       std::cout << twice(twice)(twice)(twice)(succ)(0) << '\n'; 
    }
    


  • @seldon
    Beim Raum, das hast du schön gemacht.


Anmelden zum Antworten