From 8c2ae38ebb3edddbc15682631b050e48a9e527c0 Mon Sep 17 00:00:00 2001 From: sulzmann Date: Tue, 4 Jun 2024 16:16:06 +0200 Subject: [PATCH] Delete lec-cpp-advanced-fp.html --- lec-cpp-advanced-fp.html | 863 --------------------------------------- 1 file changed, 863 deletions(-) delete mode 100644 lec-cpp-advanced-fp.html diff --git a/lec-cpp-advanced-fp.html b/lec-cpp-advanced-fp.html deleted file mode 100644 index 6a1fb29..0000000 --- a/lec-cpp-advanced-fp.html +++ /dev/null @@ -1,863 +0,0 @@ - - - - - - - - - Funktionale Programmierung (FP) in C++ - - - - - -
-

Funktionale Programmierung (FP) in C++

-

-Martin Sulzmann -

-
-
-

Übersicht

- -

Funktionaler -Programmierstil

-

Ist das was neues?

-

Nein! Siehe Lisp aus den 50er/60er Jahren.

-

Funktionale Programmierung (FP) war immer da. War aber nie “main -stream”. Findet sich aber jetzt in den meisten Programmiersprachen (z.B. -Java, Python, …).

-
-
-

Motivation

-

Mapper int Array

-
void mapInt(int* p, int len, int f(int)) {
- for(auto i=0; i<len; i++)
-   p[i] = f(p[i]);
-}
-
-int inc(int x) { return x+1; }
-int square(int x) { return x*x; }
-
-void beispiel1() {
-   int x[5] = { 1,2,3,4,5 };
-
-   mapInt(x,5,inc);
-   mapInt(x,5,square);
-}
-

Mapper float Array

-
void mapFloat(float* p, int len, float f(float)) {
- for(auto i=0; i<len; i++)
-   p[i] = f(p[i]);
-}
-
-float incF(float x) { return x+1; }
-float squareF(float x) { return x*x; }
-
-void beispiel2() {
-   float y[5] = { 1.0,2.0,3.0,4.0,5.0 };
-
-   mapFloat(y,5,incF);
-   mapFloat(y,5,squareF);
-}
-

(Funktionale) Abstraktion über die “map” Funktion.

-

Effektiv identisch zur “int” Version.

-

Generische Variante

-
template<typename T>
-void map(T* p, int len, T f(T)) {
- for(auto i=0; i<len; i++)
-   p[i] = f(p[i]);
-}
-
-void beispiel3() {
-   int x[5] = { 1,2,3,4,5 };
-   float y[5] = { 1.0,2.0,3.0,4.0,5.0 };
-
-   map<int>(x,5,[](int i) -> int { return i+1; });
-   map<float>(y,5,[](float f) -> float { return f*f; });
-}
-

Funktionale Abstraktionen kommen oft in Kombination mit -“generics”.

-

“map” Funktionen werden als anonyme Funktion (aka “lambda” Notation) -dargestellt.

-
-
-

Lambda notation

-

Bevor wir uns FP in C++ anschauen, zuerst ein paar Beispiele in -Lambda Notation.

-

Betrachte

-
f = \x -> x + 1
-

Wir verwenden hier die Lambda Notation zur Darstellung von -anonymen Funktionen.

-

Betrachte folgende Lambda Funktion zur Inkrementierung einer -beliebigen Zahl.

-
\x -> x + 1
-

Beschreibung der Syntax

-
\x
- -
->
- -
x + 1
- -

Zusammengefasst.

-
f = \x -> x + 1
-
-    ^^^^^^^^^^^^
-
-    Lambda Funktion = anonyme Funktion (Funktion ohne Namen)
-

Die Lambda Funktion ist gebunden an die Variable f.

-

Funktionsauswertung

-

Auswertung von f(1).

-

Wir schreiben “=>” fuer einen Auswertungsschritt.

-
    f(1)
-=> (\x -> x + 1)   (1)
-   ^^^^^^^^^^^^    ^^^^^
-    Funktion       Argument
-
-=> (Ersetze Parameter x durch das Argument 1)
-
-   1 + 1
-
-=> 2
-

Weiteres Beispiel.

-
y = 1
-
-f = \ x -> x + y
-

Beachte. Die Lambda Funktion \ x -> x + y hat -Paramter x und verweist auf die “freie” Variable -y.

-

D.h. im Funktionsrumpf können wir auf Variable verweisen, welche in -einem äusseren Sichtbarkeitsbereicht definiert sind.

-

f(1) liefert 2

-

weil

-
       f(1) =>  x + y wobei gilt x = 1
-            =>  1 + y
-            =>  1 + 1 weil in y der Wert 1 gespeichert ist
-            => 2
-

Partielle Funktionsanwendung

-

Betrachte

-
sum1 = \(x,y) -> x + y
-
-
- sum1(3,4) => 7
-

Parameter von sum1 ist ein Paar von Werten.

-

Betrachte

-
sum2 = \x -> (\y -> x + y)
-
-(sum2(3))(4)
-

sum2(3) liefert eine Funktion! Dies wird auch partielle -Funktionsanwendung (“partial -application” genannt.

-
sum2(3) => \y -> 3 + y
-

Deshalb

-
(sum2(3))(4) => (\y -> 3 + y)(4) => 3 + 4 => 7
- -

Anstatt

-
(sum2(3))(4)
-

können wir kürzer schreiben

-
sum2(3)(4)
-

Klammern können “links” weggelassen werden. Die Annhame ist das -Funtionsanwendung links-assoziativ ist.

- -

Betrachte folgendes Java Beispiel.

-
(obj1.meth1()).meth2()
- -

In Java ist dies als Methodenverkettung (method -chaining).

-

Methodenverkettung ist links-assoziativ. Deshalb können wir obigen -Programmcode wie folgt schreiben.

-
obj1.meth1().meth2()
-
-
-

Capture by copy versus capture by reference

-

Betrachte

-
y = 1
-
-f = \ x -> x + y
-
-y = 2
-
-z = f(1)
-

Wir definieren zuerst y dann f. Danach -überschreiben wir den Wert gespeichert in y mit -2.

-

Welchen Wert liefert die Auswertung von f(1)? Was ist der Wert von -z?

-

Es kommt darauf an wie wir die “freie” Variable y in der -Definition von f behandeln.

-

Es gibt hier 2 Möglichkeiten.

- -

Capture by copy

-

capture by copy, bedeutet Werte von freien Variable werden -kopiert.

-

Betrachte

-
    y = 1
-    f = \ x -> x + y   // An der Stelle wir ersetzen effektiv y durch den Wert 1.
-                       // Aus der Sicht des Compilers gilt:
-                       // f = \x -> x + 1
-
-    y = 2
-    z = f(1)
-

f(1) liefert 2 weil

-
           f(1) => x + y wobei x = 1
-                => 1 + y wobei die Annahme capture by copy gilt, der kopierte Wert von y ist hier 1
-                => 1 + 1
-                => 2
-

Frage: Weshalb benutzt man capture by copy, anstatt direkt einen -konstanten Wert einzugtragen? Also warum der Umweg über die Variable -y?

-

Antwort: Siehe Konstanten. Besser Platzhalter (Variablen) zu -verwenden als konkrete Zahlen.

-

Capture by reference

-

capture by reference, bedeutet bei der Auswertung des Lambda -Ausdrucks, holen wir uns immer den aktuellen Wert der freien -Variablen.

-

Betrachte

-
    y = 1
-    f = \ x -> x + y   // An der Stelle merken wir uns die Referenz auf die freie Variable y
-                       // Aus der Sicht des Compilers, mehr Arbeit ist notwendig.
-                       // Wir muessen uns alle freien Variablen merken.
-
-    y = 2
-    z = f(1)
-

f(1) liefert 3 weil

-
             f(1) => x + y wobei x = 1
-                  => 1 + y wegen Annahme (b), gilt y ausgewertet liefert 2
-                  => 1 + 2
-                  => 3
-

C++

-

C++ unterstützt capture by copy und capture by reference

-

Deshalb ist die C++ Lambda Notation “kompliziert”.

-
[capture](parameters) -> return_type { function_body }
-

In den meisten anderen Programmiersprachen wird immer capture by -reference verwendet.

-
-
-

Einfache FP Beispiele in C++

-
// --std=c++14
-
-#include <functional>
-#include <stdio.h>
-#include <iostream>
-using namespace std;
-
-
-
-void mapInt(int* p, int len, int f(int)) {
- for(auto i=0; i<len; i++)
-   p[i] = f(p[i]);
-}
-
-
-int inc(int x) { return x+1; }
-int square(int x) { return x*x; }
-
-
-int test1() {
-   int x[5] = { 1,2,3,4,5 };
-
-   // Funktionen mit Namen
-   mapInt(x,5,inc);
-   mapInt(x,5,square);
-
-   // Lambdas
-   mapInt(x,5, [](int x) -> int { return x+1; });
-   mapInt(x,5, [](int x) -> int { return x*x; });
-
-   // Kombination mit auto
-   auto f = [](int x) -> int { return x+1; };
-   mapInt(x,5,f);
-
-   printf("%d",x[0]);
-   return 0;
-}
-
-void test2() {
-
-  // Entweder
-  // int x = 1;
-  // oder wir verwenden "auto".
-  auto x = 1;
-  auto f = [x] (int y) -> int { return x + y; };
-
-  cout << f(1) << " ";
-  x = 5;
-  cout << f(1) << " ";
-
-  auto g = [&x] (int y) -> int { return x + y; };
-
-  x = 2;
-  cout << g(1) << " ";
-  x = 3;
-  cout << g(1);
-}
-
-
-
-int sum(int x, int y) { return x + y; }
-
-void sumEx() {
-      cout << sum(5, 2) << endl;
-      cout << sum(10, 5) << endl;
-}
-
-// Mit lambdas.
-void sumEx1() {
-      auto sum = [](int x, int y) -> int { return x + y; };
-
-      cout << sum(5, 2) << endl;
-      cout << sum(10, 5) << endl;
-
-
-      // Beachte. Inferenz von Rueckgabe Typ ist moeglich.
-
-      auto sum2 = [](int x, int y) /* -> int */ { return x + y; };
-
-      cout << sum2(5, 2) << endl;
-      cout << sum2(10, 5) << endl;
-
-}
-
-// Partial function application.
-// Uncurried form. See https://wiki.haskell.org/Currying
-// The lambda bound variable x appears in the capture list of the inner lambda.
-void sumEx2() {
-
-      // Funktion welche eine Funktion hat Rueckgabewerthat!
-
-      // Betrachte std::function<int(int).
-      // Beschreibt eine Funktion mit Argumenttyp int und Rueckgabetyp int.
-      // In der Mathematik geschrieben als "int -> int".
-      // In C++, ist "function" ein Template zur Beschreibung solcher Funktionsraeume.
-      // Das ganze ist definiert im Name space "std".
-
-      function<function<int(int)>(int)> sum;
-      //         int -> int
-      //  int -> (int -> int) ist der Typ von sum !!!
-      // Im allgemeinen gilt:
-      //  function<B(A)>  beschreibt den Funktionstyp "A -> B".
-
-      sum = [](int x) -> function<int(int)> { return [x](int y) -> int { return x + y; }; };
-      // 1.   Auesserer Lambda-Ausdruck, hat Parameter x vom Typ int
-      // 2.                     Rueckgabetyp ist ein Lambda Funktionstyp!
-      // 3.                                                    Innerer Lambda-Ausdruck!
-      // 4.                                                    Parameter y vom Typ int.
-
-
-
-
-      // Liefert Fehler!
-      // Weil sum, d.h. der aeussere Lambda-Ausdruck erwartet "int x" als Parameter!
-      // cout << sum(5,2) << endl;
-
-      // cout << sum(5) << endl;
-      // Beachte:
-      //  Welchen Wert liefert sum(5) ???
-      // sum(5) liefert eine Funktion zurueck!
-      // Deshalb liefert "cout << sum(5);" einen Compilerfehler.
-
-      // cout << (sum(5))(2) << endl;
-
-      // Wieso erhalten wir 7?
-      // 1. sum(5) liefert effektiv den Lambda-Ausdruck "\y -> 5 + y".
-      // 2. sum(5) ist eine Funktion, wir fuettern diese Funktion mit einem weiteren Argument.
-      // 3.     (sum(5))      (2)
-      //        ^^^^^^^^      ^^^^^
-      //        Funktion      Argument
-      // 4. Deshalb erhalten wir 7 als Ergebnis!
-
-      // Beispiel einer partiellen Funktionsanwendung.
-
-      // ZWISCHENDURCH: In C++/Java gilt folgendes.
-      // Im Fall von Methodenaufrufen, betrachte z.B.
-      //  (obj.m1()).m2()
-      //   obj unterstuert die Methode m1 und liefert ein Objekt zurueck
-      //   auf welches wir Methode m2 anwenden.
-      //  Abkuerzendende Schreibweise: obj.m1().m2()
-      //  Annahme ist, dass Methodenaufrufe sind links-assoziativ.
-
-      //  Obige Annahme fuer Methodenaufrufe wird auch angewandt
-      //  fuer Funktionsanwendungen.
-      //  Annahme ist, dass Funktionsanwendung sind links-assoziativ.
-      //  Deshalb anstatt (sum(5))(2) koennen wir kuerzer schreiben:
-
-      cout << sum(5)(2) << endl;
-
-}
-
-// Spielereien.
-void sumEx3() {
-
-  // Maximale Typinferenz!!!
-  auto sum = [](int x) /* -> std::function<int(int)> */ { return [x](int y) { return x + y; }; };
-
-  auto inc = sum(1);
-
-  cout << inc(2) << endl;
-  // Effektiv das gleiche wie
-  // cout << sum(1)(2) << endl;
-
-}
-
-void test3() {
-  // sumEx();
-  // sumEx1();
-  // sumEx2();
-  // sumEx3();
-
-}
-
-
-int main() {
-  // test1();
-  // test2();
-  // test3();
-
-}
-
-
-

FP Version von count

-
#include <functional>
-#include <stdio.h>
-#include <iostream>
-using namespace std;
-
-////////////////////////////////////
-// Zaehlen von Woertern.
-// Wort = Sequenz von Zeichen welche nicht ' ' (Leerzeichen/Blank) entaehlt.
-
-
-
-// C Version
-
-
-char* skipBlanks(char* input) {
-  while(input[0] == ' ')
-    input++;
-  return input;
-}
-
-char* scanWord(char* input) {
-  while(*input != '\0' && *input != ' ')
-    input++;
-  return input;
-}
-
-
-int count(char* input) {
-  int cnt = 0;
-
-  while(*input != '\0') {
-    input = skipBlanks(input);
-    if(*input != '\0') {
-      cnt++;
-      input = scanWord(input);
-    }
-  }
-  return cnt;
-}
-
-// C++ strings
-
-int countString(string s) {
- char cString[s.size() + 1];
-
- s.copy(cString, s.size() + 1);
- cString[s.size()] = '\0';
-
- return count(cString);
-}
-
-
-////////////////////////////////////
-// Funktionale Version
-
-
-std::function<string(string)> skip(std::function<bool(char)> p)  {
-  return [p](string s) -> string  {
-    if (s.length() == 0) {
-      return s;
-    } else if(p(s[0])) {
-    string s2 = s.substr(1);
-    return skip(p)(s2);
-    } else {
-    return s;
-    }
-  };
-}
-
-
-int countFP(string input) {
-  int cnt = 0;
-
-  auto skipBlanks = skip([](char c) -> bool { return c == ' '; });
-
-  auto scanWord = skip([](char c) -> bool { return c != ' ';});
-
-  while(input.length() != 0) {
-    input = skipBlanks(input);
-    if(input.length() != 0) {
-      cnt++;
-      input = scanWord(input);
-    }
-  }
-  return cnt;
-}
-
-void testCount(std::function<int(string)> countFunc) {
-  string tests[3] = { "", "dfd ", " dfjkd .s22?? " };
-
-  for(int i=0; i <3; i++) {
-    cout << "\n" << countFunc(tests[i]);
-  }
-}
-
-int main() {
-  testCount(countString);
-  testCount(countFP);
-}
-
-
-

Nochmal Stack Machines

- -
// Simple stack-based VM.
-
-#include <vector>
-#include <stack>
-#include <iostream>
-#include <string>
-using namespace std;
-
-// VM code
-typedef enum {
-  PLUS,
-  MULT,
-  ONE,
-  TWO
-} Code_t;
-
-string show(Code_t c) {
-  switch(c) {
-  case ONE: return "1";
-  case TWO: return "2";
-  case PLUS: return "+";
-  case MULT: return "*";
-  }
-}
-
-string show(vector<Code_t> cs) {
-  string s;
-  for(int i=0; i < cs.size(); i++) {
-    s = s + show(cs[i]);
-    if(i < cs.size() - 1) {
-      s = s + " ";
-    }
-  }
-  return s;
-}
-
-class VM {
-    vector<Code_t> code;
-  public:
-    VM(vector<Code_t> c) : code(c) {}
-
-  void showAndRun() {
-    cout << "\n Exp: " << exp();
-    cout << "\n VM code: " << show(code);
-    cout << "\n => " << run();
-  }
-
-  void showAndRun2() {
-    cout << "\n Exp: "
-     << exec<string>(
-         [](Code_t x) -> string {
-           return show(x);
-         },
-         [](string x, Code_t op, string y) -> string {
-           switch(op) {
-               case PLUS: return ("(" + x + " + " + y + ")");
-               case MULT: return ("(" + x + " * " + y + ")");
-           default: return ""; // should never apply
-           }
-             });
-    cout << "\n VM code: " << show(code);
-    cout << "\n => "
-     << exec<int>(
-         [](Code_t x) -> int {
-               switch(x) {
-               case ONE: return 1;
-           case TWO: return 2;
-           default: return -1; // should never apply
-           }
-         },
-         [](int x, Code_t op, int y) -> int {
-           switch(op) {
-               case PLUS: return (x+y);
-               case MULT: return (x*y);
-           default: return -1; // should never apply
-           }
-             });
-  }
-
-/*
-
-Methoden run sind exp sind von der Struktur sehr aehnlich.
-
-Wir abstrahieren den gemeinsamen Kern in der exec Methode
-welche in einer apply Funktion parameterisiert ist.
-
-Durch geeignet Wahl der apply Funktion erhalten wir run und exp.
-
-*/
-
-  int run() {
-    stack<int> s;
-
-    for(int i = 0; i < code.size(); i++) {
-    switch(code[i]) {
-    case ONE:
-      s.push(1);
-        break;
-    case TWO:
-      s.push(2);
-        break;
-    case MULT: {
-      auto right = s.top();
-      s.pop();
-      auto left = s.top();
-      s.pop();
-      s.push(left * right);
-      break;
-    }
-    case PLUS: {
-      auto right = s.top();
-      s.pop();
-      auto left = s.top();
-      s.pop();
-      s.push(left + right);
-      break;
-    }
-    } // switch
-      } // for
-
-      return s.top();
-  } // run
-
-  string exp() {
-    stack<string> s;
-
-      for(int i = 0; i < code.size(); i++) {
-    switch(code[i]) {
-    case ONE:
-      s.push("1");
-        break;
-    case TWO:
-      s.push("2");
-        break;
-    case MULT: {
-      auto right = s.top();
-      s.pop();
-      auto left = s.top();
-      s.pop();
-      s.push("(" + left + " * " + right + ")");
-      break;
-    }
-    case PLUS: {
-      auto right = s.top();
-      s.pop();
-      auto left = s.top();
-      s.pop();
-      s.push("(" + left + " + " + right + ")");
-      break;
-    }
-    } // switch
-      } // for
-
-      return s.top();
-  } // run
-
-  template<typename T>
-  T exec(std::function<T(Code_t)> convert, std::function<T(T,Code_t,T)> apply) {
-    stack<T> s;
-
-      for(int i = 0; i < code.size(); i++) {
-    switch(code[i]) {
-    case ONE:
-      s.push(convert(ONE));
-        break;
-    case TWO:
-      s.push(convert(TWO));
-        break;
-    case MULT: {
-      auto right = s.top();
-      s.pop();
-      auto left = s.top();
-      s.pop();
-      s.push(apply(left,MULT,right));
-      break;
-    }
-    case PLUS: {
-      auto right = s.top();
-      s.pop();
-      auto left = s.top();
-      s.pop();
-      s.push(apply(left,PLUS,right));
-      break;
-    }
-    } // switch
-      } // for
-
-      return s.top();
-  } // run
-
-};
-
-
-// Examples
-
-void testVM() {
-
-  {
-    vector<Code_t> cs{
-      ONE, TWO, TWO, PLUS, MULT
-    };
-
-
-    VM(cs).showAndRun();
-  }
-
-  {
-    vector<Code_t> cs{
-      ONE, TWO, PLUS, TWO, MULT
-    };
-
-
-    VM(cs).showAndRun();
-  }
-
-
-}
-
-
-
-int main() {
-
-  testVM();
-}
-
- -