Julia: Ganz kleines JavaScript-Programm

0 49

Ganz kleines JavaScript-Programm

Julia
  • javascript
  1. 1
    JürgenB
    1. 0
      Julia
      1. 0
        Felix Riesterer
        1. 0
          J o
  2. 0
    J o
    1. 0
      Julia
  3. 5
    Orlok
    • javascript
    • programmiertechnik
  4. 3
    Felix Riesterer
    1. 2
      dedlfix
      1. 0
        pl
    2. 2

      Scope

      Orlok
      • javascript
      • perl
      1. 0
        Felix Riesterer
        1. 0
          pl
          1. 0
            Felix Riesterer
            1. 0
              pl
              1. 0
                Felix Riesterer
      2. 0
        pl
        1. 0
          pl
          • perl
        2. 1
          Tabellenkalk
          • perl
          1. 0
            pl
            1. 1
              Tabellenkalk
        3. 0

          Schönheitswettbewerb

          1unitedpower
          • programmiertechnik
          1. 0
            pl
            1. 0
              1unitedpower
              1. 0
                pl
                1. 0
                  1unitedpower
                  1. 0
                    pl
                    1. 4
                      1unitedpower
                      1. 0
                        Matthias Apsel
                        1. 1
                          1unitedpower
                          1. 0
                            pl
                            1. 0
                              Matthias Apsel
                            2. 0
                              1unitedpower
                              1. 0
                                pl
                                1. 0
                                  pl
                                  1. 0
                                    1unitedpower
                                2. 0
                                  1unitedpower
                                  1. 0
                                    pl
                                    1. 0
                                      1unitedpower
                      2. 0
                        Rolf B
                        1. 0
                          1unitedpower
                          1. 0
                            Rolf B
                            1. 0
                              1unitedpower
                        2. 0
                          pl
      3. 0

        Dynamic Scope

        pl
  5. 0
    pl
    1. 0
      Matthias Apsel
  6. 1
    Es

Hallo Forum,

ich versuche gerade mich mit JavaScript (für die Klausur) ein bisschen anzufreunden und es fängt direkt gut an.

Ich habe folgendes Programm und muss die Ausgabe bestimmen:

function f1(x) {
return y => x * y;
};
const f3 = f1(3);
const f5 = f1(5);

alert(f3(11) + " " + f5(7));

Ausgabe: 33 35

Wie kommt es zu dieser Ausgabe? Es ist mir klar, dass 3*11=33 und 5*7=35. Aber das ganze Konstrukt ist mir vollkommen schleierhaft.

Kann mir vielleicht jemand erklären, wie das Programm hier vorgeht? Oder einen guten Link für den Anfänger?

Schönen Dank im Voraus!

Julia

  1. Hallo Julia,

    f1 ist eine Funktion, die als Rückgabe eine Funktion liefert.

    Gruß
    Jürgen

    1. Hallo Jürgen,

      vielen Dank für Deine Antwort!

      Jetzt wird es ein bisschen klarer. Wir speichern also diesen Rückgabewert (also in diesem Fall eben die Funktion) in f3 bzw. in f5 und dann rufen wir f3 und f5 mit bestimmen Werten für y auf und erhalten die Ausgabe.

      Ja, gar nicht mal so schwer, aber intuitiv ist trotzdem anders.

      Noch mal danke und viele Grüße!

      Julia

      1. Liebe Julia,

        aber intuitiv ist trotzdem anders.

        was wolltest Du denn erreichen, um diesen Code als Weg zum Ziel zu entwerfen?

        Liebe Grüße,

        Felix Riesterer.

        1. Hey,

          was wolltest Du denn erreichen, um diesen Code als Weg zum Ziel zu entwerfen?

          Schätze das ist eine Übungs oder Klausuraufgabe um zu verdeutlichen was möglich ist.

          Gruß
          Jo

  2. Hey,

    function f1(x) {
    return y => x * y;
    };
    const f3 = f1(3);
    const f5 = f1(5);
    
    alert(f3(11) + " " + f5(7));
    

    Ausgabe: 33 35

    Wie kommt es zu dieser Ausgabe? Es ist mir klar, dass 3*11=33 und 5*7=35. Aber das ganze Konstrukt ist mir vollkommen schleierhaft.

    Kann mir vielleicht jemand erklären, wie das Programm hier vorgeht? Oder einen guten Link für den Anfänger?

    Falls die Antwort von JürgenB noch nicht deutlich genug war, hier nochmal etwas anders.

    Schön auch das es eine rein Mathematische Funktion ist, so lässt es sich leicht erklären. Denn rein Mathematisch ist es nicht anders. Eine Funktion $$f(x) = x$$, hat für jedes $$x$$ ein bestimmtes Ergebnis. Für die genannte Funktion gilt $$y = x$$. Für deine Gleichung gilt wie du erkannt hast $$f(x) = f(x) * x$$. So jetzt der schöne Teil.

    In f3 sowie f5 speicherst du die Funktion selbst mit x = 3 bzw. 5, dass führt zu f3 = 3 * f1(x) und f5 = 5 * f1(x)

    Wenn du jetzt f3(11) aufrufst ist das Ergebnis f3 = 3 * f1(11). Und damit f3 = 3*11 und analog für f5.

    Gruß
    Jo

    1. Hallo Jo,

      vielen Dank für Deine Antwort!

      Eigentlich habe ich dank der Antwort von JürgenB die Funktionsweise schon nachvollziehen können.

      Aber es ist trotzdem eine super Idee, sich die Funktion noch mal als mathematische Funktion anzuschauen.

      Noch mal danke und viele Grüße

      Julia

  3. Hallo Julia

    Ich habe folgendes Programm und muss die Ausgabe bestimmen:

    function f1(x) {
      return y => x * y;
    };
    const f3 = f1(3);
    const f5 = f1(5);
    
    alert(f3(11) + " " + f5(7));
    

    Ausgabe: 33 35

    Wie kommt es zu dieser Ausgabe?

    Wie schon richtig angemerkt wurde, hast du in f1 eine Funktion, die eine Funktion als Ergebnis zurückgibt. Genauer gesagt wird eine anonyme Funktion zurückgegeben, für die bei jedem Aufruf von f1 ein neues Funktionsobjekt erstellt wird. Es ist wichtig für das Verständnis, dass hier immer eine neue Funktion erzeugt und zurückgegeben wird:

    function f1(x) {
      return y => x * y;
    }
    
    f1(3) === f1(3); // false
    

    Funktionen, die andere Funktionen als Argumente entgegennehmen oder die wie in deinem Beispiel eine Funktion als Wert zurückgeben, nennt man Funktionen höherer Ordnung. In Sprachen wie JavaScript, die es erlauben mit Funktionen im Wesentlichen so zu verfahren wie mit anderen Werten auch, die es also zum Beispiel erlauben Funktionen in Variablen zu hinterlegen oder sie wie hier innerhalb einer Rückgabeanweisung zu definieren, werden solche Konstrukte häufig genutzt um über bestimmte Abläufe zu abstrahieren.

    In diesem Fall hast du originär eine binäre Operation, nämlich die skalare Multiplikation wie man sie aus der Schulmathematik kennt. Wolltest du hier mehrfach mit demselben Faktor multiplizieren, müsstest du diesen grundsätzlich immer angeben:

    2 * 5; // 10
    2 * 7; // 14
    2 * 8; // 16
    

    Das wird schnell repetitiv und mit jeder Wiederholung erhöht sich die Wahrscheinlichkeit, dass du einen Fehler einbaust. Bist du also in der Situation, öfter mit einem bestimmten Wert multiplizieren zu müssen, bietet es sich an, diese Aufgabe in eine eigene Funktion auszulagern. Das könnte dann etwa so aussehen:

    function multiply2(x) {
      return 2 * x;
    }
    
    multiply2(5); // 10
    

    Das sieht schon besser aus, aber was, wenn es auch mehrere Stellen gibt, an denen du den Wert 3 als Faktor verwenden willst, schreibst du dir dann eine Funktion multiply3? Hier merkst du, dass du eigentlich eine Funktion möchtest, die es dir erlaubt, einen beliebigen Faktor festzusetzen, die also über die Aufgabe „multipliziere mit einer bestimmten Zahl“ abstrahiert. Das Ergebnis dieser Überlegungen ist dann deine Funktion f1.

    Aber wie funktioniert das Festlegen des einen Operanden genau?

    Stellen wir uns kurz den allgemeinen Ablauf vor: Du rufst die Funktion f1 mit einem Wert auf, sagen wir 5. Wird die Kontrolle vom aufrufenden Kontext an die aufgerufene Funktion f1 übergeben, dann wird der Wert 5 an den formalen Parameter x gebunden. Damit wird aber zunächst gar nichts gemacht. Die einzige Anweisung der Funktion besteht darin, eine anonyme Funktion zu erzeugen und sie an den Aufrufer zurückzugeben. Interessant wird es erst, wenn nun diese von f1 zurückgegebene einstellige Funktion aufgerufen wird:

    function f1(x) {
      return y => x * y;
    }
    
    const f5 = f1(5);
    
    f5(3); // 15
    

    Der Parameter y der anonymen Funktion wird hier mit dem Wert 3 initialisiert, das heißt, in dem arithmetischen Ausdruck wird y durch 3 ersetzt. Es steht da aber auch noch ein x, und ein solches ist innerhalb der Funktion, die jetzt f5 heißt, nicht definiert.

    Nun ist es so, dass Funktionen in gewisser Weise über ein Gedächtnis verfügen. – Sie merken sich, in welcher Umgebung sie definiert wurden. Das bedeutet, auch wenn Funktionen wie oben beschrieben Bürger erster Klasse sind und beliebig zwischen verschiedenen Teilen eines Programms hin- und hergeschoben werden können, können sie weiterhin auf Variablen zugreifen, die zum Zeitpunkt ihrer Erzeugung für die Funktion sichtbar waren.

    Als die Funktion f5 erzeugt wurde, existierte in ihrer lexikalischen Umgebung, nämlich dem Gültigkeitsbereich, der mit dem Funktionskontext von f1 assoziiert war, eine Variable namens x, die an den Wert 5 gebunden war. (Aus Sicht des Körpers einer Funktion ist ein Parameter nichts anderes als eine lokale Variable.) Diese Variable x wurde nach der Abarbeitung von f1 nicht verworfen, sondern konserviert.

    Beim Aufruf von f5 wird nun zunächst geprüft, ob in f5 selbst eine Variable bzw. ein Parameter mit dem Bezeichner x existiert. Da dies offenbar nicht der Fall ist, wird in dem Gültigkeitsbereich nachgesehen, in dem f5 definiert wurde. Dort wird eine entsprechende Variable gefunden und deren Wert 5 in den Ausdruck eingesetzt:

    const f5 = f1(5); // y => 5 * y
    
    f5(3); // 3 => 5 * 3
    

    Greift eine Funktion wie hier f5 auf Variablen oder Funktionen zurück, die in der Umgebung definiert waren, in der die Funktion erzeugt wurde, dann spricht man auch von einem Funktionsabschluss, oder im Englischen: Closure. Diese Funktionalität ist Grundlage für Techniken wie Currying – die Zerlegung einer mehrstelligen Funktion in mehrere einstellige Funktionen. Dafür ist deine Funktion f1 ein Beispiel.

    Viele Grüße,

    Orlok

    --
    „Dance like it hurts.
    Make love like you need money.
    Work when people are watching.“ — Dogbert
  4. Liebe Julia,

    function f1(x) {
    return y => x * y;
    };
    const f3 = f1(3);
    const f5 = f1(5);
    
    alert(f3(11) + " " + f5(7));
    

    ich schreibe das mal in "altmodisches" JavaScript um:

    function f1 (x) {
      return function (y) {
        return x * y;
      };
    }
    
    const f3 = f1(3);
    const f5 = f1(5);
    
    alert(f3(11) + " " + f5(7)); // 33 35
    

    So kann man sehen, dass eine anonyme Funktion zurückgegeben wird. Man kann auch sehen, dass das zurückgegebene Funktionsobjekt die Variable x "kennt" (closure) und bei jedem Aufruf benutzt.

    Liebe Grüße,

    Felix Riesterer.

    1. Tach!

      function f1(x) {
      return y => x * y;
      };
      const f3 = f1(3);
      const f5 = f1(5);
      
      alert(f3(11) + " " + f5(7));
      

      ich schreibe das mal in "altmodisches" JavaScript um:

      function f1 (x) {
        return function (y) {
          return x * y;
        };
      }
      
      const f3 = f1(3);
      const f5 = f1(5);
      
      alert(f3(11) + " " + f5(7)); // 33 35
      

      So kann man sehen, dass eine anonyme Funktion zurückgegeben wird.

      Das konnte man vorher auch schon sehen. Das Problem sehe ich daran nur, wenn man wenig Übung mit der Fat-Arrow-Syntax hat. Man sollte sich auch hüten, beide Schreibweisen als Ersatz anzusehen. Sie sind nämlich dann nicht mehr austauschbar, wenn ein this ins Spiel kommt. Die Fat-Arrow-Syntax kapselt das this vom Erstellungszeitpunkt, bei der herkömmlichen Schreibweise verweist this auf den aktuellen Aufruferkontext.

      Der eigentliche Knackpunkt ist meines Erachtens jedoch die Benennung der Funktion(en). Der sollte aussagekräftiger als f1, f3 und f5, so dass man nicht erst analysieren muss, was die Funktion macht, sondern das idealerweise aus ihrem Namen heraus ablesen kann.

      Damit wären wir dann bei den zwei Hauptproblemen des Programmierens:

      • Cache Invalidation
      • Naming Things
      • Off-By-One Errors

      dedlfix.

      1. Tach!

        Damit wären wir dann bei den zwei Hauptproblemen des Programmierens:

        • Cache Invalidation
        • Naming Things
        • Off-By-One Errors

        Interessant! Ich hab mal in einer Firma gearbeitet, da war die Frage wieviele Leerzeichen ein Tablulator hat das Hauptproblem. Dicht gefolgt von einer Weisung (abmahnpflichtig!) daß Kommentare keine Umlaute enthalten dürfen, die Scriptdateien jedoch in UTF-8 gespeichert werden sollen.

        MfG

    2. Hallo Felix

      function f1(x) {
        return y => x * y;
      }
      
      const f5 = f1(5);
      
      f5(7); // 35
      

      Man kann auch sehen, dass das zurückgegebene Funktionsobjekt die Variable x "kennt"

      Das kann man sehen, weil JavaScript statischen Scope besitzt – und JavaScript besitzt statischen Scope, damit man sowas sehen kann. ;-)


      Schauen wir uns einmal an, wie Julias Beispiel in Perl[1] aussehen könnte:

      sub f1($x) {
        return -> $y {$x * $y}
      }
      
      my &f5 = f1(5);
      
      &f5(7); # 35
      

      Ich denke, die Syntax ist ähnlich genug, so dass es keiner allzu großen Erklärungen bedarf. Wir deklarieren mit sub eine gewöhnliche Funktion mit einem Parameter. Das Sigil $ vor Parameter- bzw. Variablennamen zeigt an, dass ein skalarer Wert gespeichert werden soll. Wie in der JavaScript-Vorlage geben wir eine anonyme Funktion zurück, und zwar mit einer gebundenen Variable y und einer freien Variable x. (Das Schlüsselwort return hätten wir uns dabei eigentlich sparen können, da ohne eine explizite Anweisung der Wert des letzten Ausdrucks zurückgegben wird.)

      my &f5 = f1(5);
      

      Hier rufen wir f1 auf und speichern die zurückgegebene anonyme Funktion in einer Variable. Das Sigil & vor dem Variablennamen zeigt an, dass ein Objekt referenziert werden soll, das aufgerufen werden kann. Im Anschluss an die obige Anweisung und analog zur Vorlage rufen wir die in f5 hinterlegte Funktion dann mit dem Wert 7 auf. Da Perl ebenso wie JavaScript standardmäßig die Regeln für statischen bzw. lexikalischen Scope anwendet, wird hier auch dasselbe Ergebnis zurückgegeben:

      &f5(7); # 35
      

      Im Gegensatz zu den allermeisten anderen Programmiersprachen besteht in Perl aber die Möglichkeit, andere Regeln für die Sichtbarkeit von Variablen festzulegen. Wird zwischen Sigil und Bezeichner einer Variablen ein * notiert, dann werden auf diese Variable die Regeln für dynamischen Scope angewendet. Schauen wir uns am Ausgangsbeispiel an, welche Konsequenzen das hat:

      my $*x = 4;
      
      
      sub f1($*x) {
        return -> $y {$*x * $y}
      }
      
      
      my &f5 = f1(5);
      

      Hier haben wir an drei Stellen Veränderungen vorgenommen. Wir haben im globalen Gültigkeitsbereich nun eine Variable x mit dem Wert 4, deren Sichtbarkeit nicht statisch, sondern dynamisch bestimmt wird. Außerdem haben wir sowohl in der Parameterliste von f1 als auch innerhalb der anonymen Funktion, die von f1 zurückgegeben wird, für x zwischen Sigil und Bezeichner ein * notiert. Wie schon im ersten Beispiel oben rufen wir f1 mit dem Wert 5 auf und speichern das Ergebnis in der Variable f5. Wenn wir diese Funktion nun wie oben mit dem Wert 7 aufrufen, kommt folgendes Ergebnis dabei heraus:

      my &f5 = f1(5);
      
      &f5(7); # 28
      

      Ups. Was ist hier passiert? :-)

      Offenbar hat die innerhalb von f1 erzeugte und in der Variable f5 gespeicherte Funktion den Parameter x aus dem lokalen Scope der Funktion f1 ignoriert und bei der Auswertung des arithmetischen Ausdrucks stattdessen die globale Variable x referenziert!

      my $*x = 4;
      

      Anders als bei statischem Scope, wird bei dynamischem Scope die Sichtbarkeit eines Bezeichners erst zur Laufzeit des Programms bestimmt. Der Gültigkeitsbereich von Variablen ist hier in erster Linie nicht räumlich, sondern zeitlich definiert. Ob also eine Bindung für einen Bezeichner existiert – und falls ja, welcher Wert mit diesem Bezeichner verknüpft ist, hängt vom Zeitpunkt der Programmausführung ab, das heißt davon, welche Funktionen in welcher Reihenfolge aufgerufen wurden.

      &f5(7); # 28
      

      Wir rufen die in f5 hinterlegte Funktion aus dem globalen Scope auf und übergeben dabei den Wert 7, der an den einzigen Parameter y gebunden wird. Dann passiert zunächst das gleiche, was auch schon vorher passiert ist: Es wird festgestellt, dass innerhalb der Funktion f5 keine Bindung für den Bezeichner x existiert. Nun wird jedoch nicht in der lexikalischen Umgebung, in der f5 definiert wurde, nach x gesucht, sondern in dem Gültigkeitsbereich der mit dem aufrufenden Kontext verknüpft ist, in diesem Fall also im globalen Scope. Dort haben wir eine Variable x definiert und folglich wird der Wert dieser Variable in den Ausdruck eingesetzt.


      Betrachten wir noch ein weiteres Beispiel, um den Ablauf zu verdeutlichen:

      # Global execution context
      
      my $*x = 10;
      
      
      # Create local variable and call other function
      
      sub f {
        my $*x = 20;
        g();
      }
      
      
      # Add then print to stdout
      
      sub g {
        say $*x += 10;
      }
      

      Wie im letzten Beispiel haben wir hier zunächst eine globale Variable namens x definiert. Dazu kommen zwei Funktionen, f und g, die beide keine Argumente erwarten. (In diesem Fall können in Perl die Klammern für die Parameterliste weggelassen werden.) Die Funktion f definiert eine lokale Variable x und ruft die Funktion g auf. Diese wiederum greift auf ein x zu und addiert 10, bevor sie den aktuellen Wert von x ausgibt.

      Was passiert nun, wenn wir f aufrufen? Welcher Wert wird ausgegeben?

      +--------------+
      |    global    |
      |              |
      |    x = 10    |
      +--------------+
      

      Wenn wir das Programm starten, wird ein Stack Frame für den globalen Ausführungskontext auf den Call Stack gelegt, der wichtige Informationen über unser Programm enthält, unter anderem auch die Information, welche Variablen im globalen Gültigkeitsbereich definiert sind. Dazu gehört hier auch die Variable x, die gleich zu Anfang im globalen Scope deklariert und mit dem Wert 10 initialisiert wurde.

      +--------------+
      |      f       |
      |              |
      |    x = 20    |
      +--------------+
      |    global    |
      |              |
      |    x = 10    |
      +--------------+
      

      Rufen wir die Funktion f auf, dann wird für diesen Aufruf ein weiterer Rahmen auf den Stapel gelegt. Es wird vermerkt, dass im Körper der Funktion f eine lokale Variable namens x deklariert wurde, deren Wert 20 ist.

      +--------------+
      |      g       |
      |              |
      |              |
      +--------------+
      |      f       |
      |              |
      |    x = 20    |
      +--------------+
      |    global    |
      |              |
      |    x = 10    |
      +--------------+
      

      Aus f heraus wird g aufgerufen, und auch für diesen Aufruf wird ein Stack Frame auf den Stapel gelegt. In der Funktion g wird keine lokale Variable angelegt, aber dafür auf eine Variable x zugegriffen.

      my $*x = 10;
      
      # snip
      
      sub g {
        say $*x += 10;
      }
      

      Wenn wir diesen Teil des Codes betrachten, scheint klar, welches x in g referenziert wird: g scheint das globale x zu „kennen“. Da für x jedoch die Regeln für dynamischen Scope gelten, spielt die Tatsache keine Rolle, dass die Funktion g selbst im globalen Gültigkeitsbereich deklariert wurde. Statt dort nach einer Bindung für x zu suchen, wird der Gültigkeitsbereich durchsucht, der mit dem vorletzten Frame auf dem Stack assoziiert ist, also der lokale Scope der Funktion f, aus der heraus g aufgerufen wurde:

      +--------------+
      |      g       |
      |              |
      |              |      x += 10
      +--------------+
      |      f       |      ▲
      |              |      |
      |    x = 20    |   ___|
      +--------------+
      |    global    |
      |              |
      |    x = 10    |
      +--------------+
      

      Da innerhalb von f eine Bindung für den gesuchten Bezeichner existiert, wird das x aus diesem Gültigkeitsbereich referenziert. Es werden 10 addiert und im Ergebnis wird der Wert 30 ausgegeben – und nicht 20, was der Fall gewesen wäre, wenn die globale Variable mit dem selben Namen referenziert worden wäre. Die globale Variable wäre nur dann erreicht worden, wenn im Scope von f keine Bindung für x vorliegen würde.

      +--------------+
      |      f       |
      |              |
      |    x = 30    |
      +--------------+
      |    global    |
      |              |
      |    x = 10    |
      +--------------+
      

      Nach dem der Aufruf von g abgearbeitet wurde, wird der dazugehörige Frame vom Stack wieder entfernt. Würde innerhalb von f weiter mit x gearbeitet, dann mit dessen neuem Wert 30. Andere Funktionen, die gegebenenfalls aus diesem Kontext heraus noch aufgerufen werden, würden entsprechend dieselbe Variable referenzieren. Es sei denn, es würde bei einem der Aufrufe selbst wieder eine lokale Variable mit dem Namen x deklariert, welche die Variablen in f und im globalen Scope verschattet.

      +--------------+
      |    global    |
      |              |
      |    x = 10    |
      +--------------+
      

      Dies gilt in gleicher Weise für den globalen Ausführungskontext, wenn f terminiert und der zu dem Aufruf gehörende Frame vom Call Stack genommen wird.


      Es sei hierzu angemerkt, dass es sich bei diesem Beispiel lediglich um eine schematische Darstellung handelt, um die Funktionsweise von dynamischem Scope zu veranschaulichen. Die Ausführungen sind nicht so zu interpretieren, dass in der Praxis tatsächlich der Call Stack nach Namensbindungen durchsucht wird. Für die Buchführung darüber, welche Bindung in der Kette der Aufrufe am nächsten liegt, wird man wahrscheinlich eine separate Datenstruktur verwenden wollen.


      Jedenfalls können wir festhalten, dass man bei dynamischem Scope in aller Regel ahead of time nicht bestimmen kann, welchen Wert eine Variable hat, die außerhalb einer bestimmten Funktion definiert ist, oder ob besagte Variable überhaupt sichtbar ist. Eine statische Analyse des Codes stößt hier schnell an Grenzen, da die Sichtbarkeit von Variablen und Funktionen immer vom gegenwärtigen Zustand des Programms abhängt.

      Da Code den man nur schwer nachvollziehen kann naturgemäß Fehler begünstigt, wird dynamischer Scope selten verwendet und in den meisten Sprachen nicht einmal als optionales Feature angeboten. Stattdessen verwenden wir – vielleicht oft ohne darüber nachzudenken – ganz selbstverständlich statischen, beziehungsweise lexikalischen Scope. Also Regeln für die Sichtbarkeit von Bezeichnern, bei denen man durch bloßen Augenschein sagen kann, ob eine Funktion eine Variable „kennt“.

      Viele Grüße,

      Orlok


      --
      „Dance like it hurts.
      Make love like you need money.
      Work when people are watching.“ — Dogbert

      1. Genauer gesagt, in Perl 6. Die Syntax der Beispiele hier im Beitrag ist nicht abwärtskompatibel zu früheren Versionen der Sprache. ↩︎

      1. Lieber Orlok,

        das ist der Grund, warum ich Perl nie angefasst habe.

        Liebe Grüße,

        Felix Riesterer.

        1. hi @Felix Riesterer

          das ist der Grund, warum ich Perl nie angefasst habe.

          Nun, Perl 6 gibt es noch gar nicht so lange. Aber was hier beschrieben wurde, ist ja auch mit Perl 5 schon seit Urzeiten möglich.

          MfG

          1. Lieber pl,

            Perl 6 oder 5 hin und her, mich hat die kryptische Syntax von Perl schon immer abgeschreckt. Den Code zu lesen empfinde ich als eine Qual. Und wenn ich dann daran denke, dass ich mich in Code wieder einarbeiten muss, den ich vor einem halten Jahr geschrieben habe (selbstverständlich ausführlich kommentiert, man lernt ja dazu), dann ist mir eine gut lesbare Syntax wichtig. Da scheidet Perl für mich generell aus.

            Liebe Grüße,

            Felix Riesterer.

            1. hi @Felix Riesterer

              Perl 6 oder 5 hin und her,

              Noch einmal zum Verständnis: Perl 6 ist nicht Perl. Perl 6 hat mit dem bisherigem Perl 5 gar nichts mehr zu tun.

              mich hat die kryptische Syntax von Perl schon immer abgeschreckt.

              Mit Perl 5 kann man sehr leserlichen, also unkryptischen Code schreiben, das zeige ich ja hier zum konkreten Beispiel

              Den Code zu lesen empfinde ich als eine Qual.

              Perl 6 orientiert sich vom Syntax her an allen anderen sogenannten modernen PLs. Also den Syntax eines modernen JS ES6 zu loben und denselben Syntax in Perl6 als kryptisch zu bezeichnen ist schizophren.

              MfG

              1. Lieber pl,

                Mit Perl 5 kann man sehr leserlichen, also unkryptischen Code schreiben, das zeige ich ja hier zum konkreten Beispiel

                Dein Beispiel hat mich leider nicht überzeugt.

                Also den Syntax eines modernen JS ES6 zu loben und denselben Syntax in Perl6 als kryptisch zu bezeichnen ist schizophren.

                Habe ich jemals irgendwo ES6 gelobt??

                Liebe Grüße,

                Felix Riesterer.

      2. hi,

        Schauen wir uns einmal an, wie Julias Beispiel in Perl6 aussehen könnte:

        sub f1($x) {
          return -> $y {$x * $y}
        }
        
        my &f5 = f1(5);
        
        &f5(7); # 35
        

        Sieht furchtbar aus. In Perl 5 hingegen erklärt sich der Code von selbst:

        use strict;
        use warnings;
        
        $, = " ";
        sub f1{ 
            my $x = shift;
            sub{
                my $y = shift;
                $x * $y;
            }
        }
        
        # CODE Referenzen speichern
        my $f3 = f1(3);
        my $f5 = f1(5);
        
        # CODErefs ausführen
        print $f3->(11), $f5->(7);
        

        Scope inbegriffen. sub erzeugt eine Referenz auf den Code und die letzte Anweisung in einer sub ist immmer der return. Der Geltungsbereich lexikalischer Variablen wird mit my festgelegt und der ganze statische Scope heißt hier package main.

        Codereferenzen werden über den -> Operator ausgeführt, Argumente in ().

        MfG

        1. hi,

          Codereferenzen werden über den -> Operator ausgeführt, Argumente in ().

          Werden die Funktionen f3 und f5 als Typeglob (* Operator) referenziert, kann beim Aufruf der -> Operator auch entfallen:

          use strict;
          use warnings;
          
          local $, = " ";
          sub f1{ 
              my $x = shift;
              return sub{
                  my $y = shift;
                  $x * $y;
              }
          }
          
          # Erzeuge Typeglobs
          *f3 = f1(3);
          *f5 = f1(5);
          
          print f3(11), f5(7);
          

          Perl5

        2. Hallo,

          hi,

          Beispiel in Perl6

          Sieht furchtbar aus.

          Das stimmt.

          In Perl 5 hingegen erklärt sich der Code von selbst:

          Natürlich. Nicht.

          Gruß
          Kalk

          1. Hallo,

            In Perl 5 hingegen erklärt sich der Code von selbst:

            Natürlich. Nicht.

            Guck Dir doch an was hier für ein Roman geschrieben wurde und hier nicht

            .

            1. Hallo,

              Hallo,

              In Perl 5 hingegen erklärt sich der Code von selbst:

              Natürlich. Nicht.

              Guck Dir doch an …

              Hab ich gesehen und zugegebenermaßen nicht alles gelesen.

              Aber dass sich jemand bemüßigt fühlt, zu einem Codeschnipsel ein Tutorial zu schreiben, während ein anderer ignoriert wird, sagt nix über dessen Selbsterklärung aus.

              Gruß
              Kalk

        3. use strict;
          use warnings;
          
          $, = " ";
          sub f1{ 
              my $x = shift;
              sub{
                  my $y = shift;
                  $x * $y;
              }
          }
          
          # CODE Referenzen speichern
          my $f3 = f1(3);
          my $f5 = f1(5);
          
          # CODErefs ausführen
          print $f3->(11), $f5->(7);
          

          Ich nominiere Haskell für den Schönheitspreis. Code online ausführen

          f1 = (*)
          f3 = f1 3
          f5 = f1 5
          main = print (show (f3 11, f5 7))
          
          1. Ich nominiere Haskell für den Schönheitspreis.

            f1 = (*)
            f3 = f1 3
            f5 = f1 5
            main = print (show (f3 11, f5 7))
            

            Haskell? Das sieht aus wie eine Sommergrippe, hüstel 😉

            1. Haskell? Das sieht aus wie eine Sommergrippe, hüstel 😉

              Der Zwickersmiley lässt deine Aussage auch nicht sympathischer wirken.

              1. Haskell? Das sieht aus wie eine Sommergrippe, hüstel 😉

                Der Zwickersmiley lässt deine Aussage auch nicht sympathischer wirken.

                Programmieren ist ja auch keine Frage der Sympathie.

                MfG

                1. Haskell? Das sieht aus wie eine Sommergrippe, hüstel 😉

                  Der Zwickersmiley lässt deine Aussage auch nicht sympathischer wirken.

                  Programmieren ist ja auch keine Frage der Sympathie.

                  Über diese Attitüde solltest du nochmal meditieren.

                  1. Haskell? Das sieht aus wie eine Sommergrippe, hüstel 😉

                    Der Zwickersmiley lässt deine Aussage auch nicht sympathischer wirken.

                    Programmieren ist ja auch keine Frage der Sympathie.

                    Über diese Attitüde solltest du nochmal meditieren.

                    Das habe ich bereits hier und an anderer Stelle getan.

                    Kurz gesagt: Programmieren ist eine Frage der Lesbarkeit. Hinter diesem Begriff stecken auch solche Dinge wie Teamarbeit, Wartungsfreundlichkeit, Nachvollziehbarkeit und Skalierbarkeit. Leider gehören Zeiten, in denen diese Dinge hier täglich Thema waren, längstens der Vergangenheit an.

                    MfG

                    1. Kurz gesagt: Programmieren ist eine Frage der Lesbarkeit.

                      Dann lass uns diesen zentralen Punkt dochmal inhaltlich diskutieren. Ich mache gerne den Anfang und erkläre wieso ich das Perl-Skript in punkto Lesbarkeit und Aussdrucksstärke dem Haskell-Programm unterlegen finde. Ich bin gespannt deine Erwiderung darauf zu lesen. Hier nochmal die beiden Kandidaten im direkt Vergleich:

                      sub f1{ 
                          my $x = shift;
                          return sub{
                              my $y = shift;
                              $x * $y;
                          }
                      }
                      
                      f1 = (*)
                      

                      Das Perl-Skript ist unnötig ausschweifend, in natürlicher Sprache: „Definiere eine Subroutine namens f1, im Funktionskörper lege eine lokale Variable $x an, belege die Variable mit dem ersten Wert meines impliziten Parameter-Arrays und entferne den Wert aus selbigem, dann gib eine anonyme Subroutine zurück, in deren Funktionskörper, lege eine weitere lokale Variable $y an, belege die Variable abermals mit dem ersten Wert aus dem impliziten Parameter-Array und lösche den Wert aus dem Array, schließlich lass die innere Funktion das Produkt von $x und $y zurückgeben.“

                      Die Haskell-Definition dagegegn ist präzise und pointiert, sinngemäß „f1 ist ein Alias für die Multiplikation.“

                      Das war in natürlicher Sprache, was auf semantischer Ebene passiert. Rein syntaktisch, ist das Perl-Skript mit administrativen Symbolen aufgebläht, in Haskell ist nur das Klammernpaar um den Multiplikations-Stern administrativer Natur.

                      Zusätzlich hat die Haskell-Definition mit weniger Worten mehr zum Ausdruck gebracht: Die Funktion f1 ist typsicher, wenn sie mit einem String oder einem beliebigem nicht numerischen Wert aufgerufen wird, gibt es einen Typfehler zur Compilezeit. Außerdem funktioniert die Haskell-Variante ohne weiteres Zutun auch mit unbeschränkt großen Zahlen und beliebig genauen reellen Zahlen.

                      Abschließend komme ich zu der Einschätzung, dass das Haskell-Programm präzise und ausdrucksstark ist, das Perl-Programm dagegen geschwätizg und ausdrucksschwach. Du bist an der Reihe mich vom Gegenteil zu überzeugen.

                      1. Hallo 1unitedpower,

                        Zusätzlich hat die Haskell-Definition mit weniger Worten mehr zum Ausdruck gebracht: Die Funktion f1 ist typsicher, wenn sie mit einem String oder einem beliebigem nicht numerischen Wert aufgerufen wird, gibt es einen Typfehler zur Compilezeit. Außerdem funktioniert die Haskell-Variante ohne weiteres Zutun auch mit unbeschränkt großen Zahlen und beliebig genauen reellen Zahlen.

                        Wobei man dies natürlich nicht aus f1 = (*) herauslesen kann.

                        Bis demnächst
                        Matthias

                        --
                        Rosen sind rot.
                        1. Zusätzlich hat die Haskell-Definition mit weniger Worten mehr zum Ausdruck gebracht: Die Funktion f1 ist typsicher, wenn sie mit einem String oder einem beliebigem nicht numerischen Wert aufgerufen wird, gibt es einen Typfehler zur Compilezeit. Außerdem funktioniert die Haskell-Variante ohne weiteres Zutun auch mit unbeschränkt großen Zahlen und beliebig genauen reellen Zahlen.

                          Wobei man dies natürlich nicht aus f1 = (*) herauslesen kann.

                          Das stimmt, das zu sehen erfordert Haskell-Vorwissen, ich hätte den Typen auch ausschreiben können, um es explizit zu machen.

                          f1 :: Int -> (Int -> Int)
                          f1 = (*)
                          

                          Die Typsignatur drückt folgendes aus: „f1 ist eine Funktion, die einen Int-Wert entgegen nimmt und eine Funktion zurück gibt, die ebenfalls einen Int-Wert entgegen nimmt und einen Int-Wert zurück liefert“. Das Klammernpaar in der Typsignatur hätte ich auch Weglassen können, weil der Pfeil rechts-assoziativ ist, also:

                          f1 :: Int -> Int -> Int
                          

                          Der Typ ist jetzt allerdings spezifischer als er sein müsste, weil die Funktion nicht nur mit Int-Werten arbeiten kann, sondern mit allen numerischen Typen, die eine Multiplikation unterstützen, u.a. auch Fließkommazahlen und die reellen Zahlen. Diese Datentypen werden Haskell in einer sogenannten Typklasse Num a gesammelt, wobei das a stellvertetend für einen Typen steht. Wenn ein Typ zu dieser Typklasse gehört, sagt man auch der Typ hat eine Instanz von der Typklasse Num a. Der Int-Typ hat beispielsweise so eine Instanz, und ebenso die ganzen und rellen Zahlen. Man kann Typklassen in Haskell als Nebenbedingung in Typsignaturen benutzen. Zum Beispiel kann ich die Signatur von f1 jetzt so auflockern, dass sie mit allen numerischen Typen funktioniert:

                          f1 :: forall a . Num a => a -> a -> a
                          

                          Die Lesart ist: „Für alle Typen a, die eine Instanz von Num a haben, lass f1 eine Funktion sein, die ein a als Parameter hat, und eine Funktion zurückliefert, die wiederum ein a als Parameter hat und schließlich ein a zurück gibt“. Das forall a . ist optional in Haskell, nicht quantifizierte Typvariablen werden automatisch all-quantifiziert.

                          Eine Stärke von Haskell ist, dass man Typsignaturen nur in extrem seltenen Ausnahmefällen angeben muss, im vorliegenden Fall assoziiert Haskell f1 automatisch mit genau diesem letzten Typen. Noch besser ist, dass man sich während der Entwicklung anzeigen lassen kann, welchen Typen Haskell einem bestimmten Ausdruck zuordnet. Das Typsystem will sich dem Entwickler nicht aufdrängen, sondern ihn vor Fehlern bewahren, dazu ist es nicht notwenig den Typen auszuschreiben, es ist nur notwendig, dass der Typchecker den Programmierer mit Typinformationen versorgen kann. In vielen Programmiersprachen ist das andersrum, da hilft der Programmierer dem Compiler.

                          1. Zusätzlich hat die Haskell-Definition mit weniger Worten mehr zum Ausdruck gebracht: Die Funktion f1 ist typsicher, wenn sie mit einem String oder einem beliebigem nicht numerischen Wert aufgerufen wird, gibt es einen Typfehler zur Compilezeit. Außerdem funktioniert die Haskell-Variante ohne weiteres Zutun auch mit unbeschränkt großen Zahlen und beliebig genauen reellen Zahlen.

                            Wobei man dies natürlich nicht aus f1 = (*) herauslesen kann.

                            Das stimmt, das zu sehen erfordert Haskell-Vorwissen,

                            Das ist toll aber in Perl genauso daß man da ein gewisses Vorwissen braucht.

                            ich hätte den Typen auch ausschreiben können, um es explizit zu machen.

                            f1 :: Int -> (Int -> Int)
                            f1 = (*)
                            

                            Die Typsignatur drückt folgendes aus: „f1 ist eine Funktion, die einen Int-Wert entgegen nimmt und eine Funktion zurück gibt, die ebenfalls einen Int-Wert entgegen nimmt und einen Int-Wert zurück liefert“.

                            Multiplikation ist nicht auf Integer beschränkt. In Perl hat der * übrigens auch eine weitere Bedeutung die jedoch jeder Perlentwicker sofort sieht am Ausdruck:

                            *f1 = sub{}; # Typeglob
                            $x * $x;     # Multiplikation
                            

                            Wichtig zum Verständnis der ganzen Geschichte hier ist auch das Wissen um das statische Verhalten:

                            state $f3 = f1(3);
                            

                            was aber auch ohne das Schlüsselwort state per se so ist. In Perl kann man per local sogar mit dynamischen Scope arbeiten, hier mal alles zsamme:

                            local $, = "\n";
                            sub f1{ 
                                my $x = shift;
                                sub{
                                    my $y = shift;
                                    $x * $y;
                                }
                            }
                            
                            # CODE Referenzen speichern
                            state $f3 = f1(3);
                            our $f5 = f1(5);
                            
                            # CODErefs ausführen
                            print $f3->(11), $f5->(7), "\n"; # 33 35
                            
                            do{
                                local $f5 = f1(50);
                                print $f5->(10), "\n";   # 500
                            };
                            
                            print $f5->(10); # 50
                            

                            MfG

                            1. Hallo pl,

                              ich hätte den Typen auch ausschreiben können, um es explizit zu machen.

                              f1 :: Int -> (Int -> Int)
                              f1 = (*)
                              

                              Die Typsignatur drückt folgendes aus: „f1 ist eine Funktion, die einen Int-Wert entgegen nimmt und eine Funktion zurück gibt, die ebenfalls einen Int-Wert entgegen nimmt und einen Int-Wert zurück liefert“.

                              Multiplikation ist nicht auf Integer beschränkt.

                              Du hättest weiterlesen sollen.

                              Der Typ ist jetzt allerdings spezifischer als er sein müsste, weil die Funktion nicht nur mit Int-Werten arbeiten kann, sondern mit allen numerischen Typen, die eine Multiplikation unterstützen, u.a. auch Fließkommazahlen und die reellen Zahlen.

                              Das heißt f1 = (*) arbeitet mit allen Typen, die eine Multiplikation untestützen.

                              Bis demnächst
                              Matthias

                              --
                              Rosen sind rot.
                            2. Schade, dass du nicht auf meine Perl-Kritik eingegangen bist, sondern direkt zu anderen Beispielen gesprungen bist, aber ich bin gerne bereit, auch darauf einzugehen.

                              In Perl hat der * übrigens auch eine weitere Bedeutung die jedoch jeder Perlentwicker sofort sieht am Ausdruck:

                              Sigils spielen in Perl eine ähnliche Rolle wie Typsignaturen in Haskell. Aber mit ein paar Nachteilen: Es gibt nur eine handvoll Sigils, um zwischen Werten verschiedener Typen zu diskreminieren. Skalare $, Arrays @, Hashes %, Subroutinen & und eben den Typeglob *, unter den alle Werte fallen. Allen mangelt es an Expressivität: Mit einem $-Sigil kann ich nicht ausdrücken, dass mein Skalar eine Ganzzahl oder ein String ist. Mit einem @-Sigil kann ich nicht angeben, welchen Typen die Elemente in meinem Array haben. Mit dem &-Sigil kann ich nicht angeben, welche Parametertypen und welchen Rückgabetypen eine Subroutine hat. Mit einem %-Sigil kann ich nicht ausdrücken, welche Schlüssel-Werte-Kombinationen vorhanden sein müssen. Mit dem Typeglob * weiß ich letztendlich überhaupt nichts mehr über den Typen. In Haskell kann ich all die eben erwähnten Sachverhalte über Typsignaturen ausdrücken. Es mangelt Perl hier also im Vergleich wieder an Ausdruckssärke. Der andere große Nachteil ist, dass Perl dynamisch typisiert ist, die Typinformationen, die in den Sigils stecken, werden also erst zur Laufzeit gecheckt. In Haskell würde ein fehlerhaft typisiertes Programm nicht kompilieren und den Programmierer direkt auf den Fehler aufmerksam machen. Und wie schon erwähnt, findet der Haskell-Compiler die meisten Typen von alleine heraus, der Programmierer muss keine Typsignaturen dafür schreiben.

                              was aber auch ohne das Schlüsselwort state per se so ist. In Perl kann man per local sogar mit dynamischen Scope arbeiten, hier mal alles zsamme:

                              Ich halte dynamische Scoping-Regeln in jedem Fall für einen schweren Design-Fehler, ob in JavaScript, Java oder Perl. Dynamisches Scoping drückt aus: „Du findest hier nicht alle Informationen, die du brauchst, um diesen Code zu verstehen, weil ein paar Informationen erst zur Laufzeit eintreffen.“

                              1. Ich halte dynamische Scoping-Regeln in jedem Fall für einen schweren Design-Fehler, ob in JavaScript, Java oder Perl. Dynamisches Scoping drückt aus: „Du findest hier nicht alle Informationen, die du brauchst, um diesen Code zu verstehen, weil ein paar Informationen erst zur Laufzeit eintreffen.“

                                Dann verstehe ich unter dynamischen Scoping wahrscheinlich was Anderes. Auf jeden Fall hat das was local macht nichts damit zu tun, daß Informationen erst zur Laufzeit eintreffen. Vielmehr das Gegenteil, denn local funktioniert nur mit Informationen die bereits vorhanden sind (Symboltabelle).

                                local @a = qw(a b c d e);
                                # Global symbol "@a" requires explicit package name
                                

                                Gibt also eine Fehlermeldung, s.o., die besagt, daß der Interpreter den Scope von @a gar nicht kennt.

                                MfG

                                1. PS: Sieh auch perldoc perlsub

                                  A "local" just gives temporary values to global (meaning package) variables. It does not create a local variable. This is known as dynamic scoping. Lexical scoping is done with "my", which works more like C's auto declarations.

                                  Btw., perldoc ist eine Sache die ich an Perl ganz besonders schätze. Gibt es das in Haskill auch?

                                  MfG

                                  1. PS: Sieh auch perldoc perlsub

                                    A "local" just gives temporary values to global (meaning package) variables. It does not create a local variable. This is known as dynamic scoping. Lexical scoping is done with "my", which works more like C's auto declarations.

                                    Btw., perldoc ist eine Sache die ich an Perl ganz besonders schätze. Gibt es das in Haskill auch?

                                    Haskell übrigens. Am nächsten dran kommt dem wohl Hackage. Dort kann man die Dokumentation sowohl der Standard-Library als auch der meisten Community-Packages finden. Der Sprachkern von Haskell ist historisch bedingt hauptsächlich in wissenschaftlichen Arbeiten dokumentiert.

                                2. Dann verstehe ich unter dynamischen Scoping wahrscheinlich was Anderes. Auf jeden Fall hat das was local macht nichts damit zu tun, daß Informationen erst zur Laufzeit eintreffen.

                                  Statisch und dynamisch sind duale Begriffe, die in der Programmierung immer wieder auftreten. Statisch bedeutet fast immer "zur Compilezeit" oder "zur Entwicklungszeit". Dynamisch bedeutet "zur Laufzeit". Das gilt z.B. für dynamische vs. statische Typisierung und ganz genauso für dynamischen vs. statischen Scope. Perl macht da keine Ausnahme.

                                  1. Dann verstehe ich unter dynamischen Scoping wahrscheinlich was Anderes. Auf jeden Fall hat das was local macht nichts damit zu tun, daß Informationen erst zur Laufzeit eintreffen.

                                    Statisch und dynamisch sind duale Begriffe, die in der Programmierung immer wieder auftreten. Statisch bedeutet fast immer "zur Compilezeit" oder "zur Entwicklungszeit".

                                    Nein. Statisch hat mit Laufzeit überhaupt nichts zu tun sondern mit dem Verhalten. So verhalten sich Klassenvariablen per se statisch. In PHP ist es sogar üblich, Klassenfunktionen als statische Funktionen zu bezeichnen, eben weil sie sich statisch verhalten.

                                    Das Schlüsselwort state (static) kam übrigens mit Perl v5.10 aber statisches Verhalten gibt es auch ohne `state´.

                                    Anwendungsbeispiel:

                                    use strict;
                                    use warnings;
                                    use v5.10;
                                    
                                    print cnt(), "\n" for 1..10;
                                    sub cnt{
                                        state $c = 0;
                                        ++$c;
                                    }
                                    
                                    Das gibt aus
                                    1
                                    2
                                    3
                                    4
                                    5
                                    6
                                    7
                                    8
                                    9
                                    10
                                    

                                    Dynamischer Scope: Mit local $/ = undef wird der Wert der bisher in $/ stand, in einem dynamisch hinzuglinkten Scope gesichert. Der neue Wert, in diesem Fall undef gilt nur im aktuellen Block und wenn der verlassen wird, ist wieder der alte Wert gültig. Das steht aber auch alles in der Dokumentation.

                                    MfG

                                    1. Nein. Statisch hat mit Laufzeit überhaupt nichts zu tun sondern mit dem Verhalten.

                                      Ich sagte statischer Scope ist zur Compilezeit bekannt. Dynamischer Scope wird erst zur Laufzeit bekannt. Das ist dauch die Konvention, die die Perl-Entwickler akzeptiert haben.

                                      Aus der Wikipedia:

                                      With dynamic scope, a global identifier refers to the identifier associated with the most recent environment, and is uncommon in modern languages. In technical terms, this means that each identifier has a global stack of bindings. Introducing a local variable with name x pushes a binding onto the global x stack (which may have been empty), which is popped off when the control flow leaves the scope. Evaluating x in any context always yields the top binding. Note that this cannot be done at compile-time because the binding stack only exists at run-time, which is why this type of scoping is called dynamic scoping.

                                      Und siehe da, das genau das, was zusammengekürzt in der Perl-Dokumentation steht.

                                      A "local" just gives temporary values to global (meaning package) variables. It does not create a local variable. This is known as dynamic scoping.

                      2. Hallo 1unitedpower,

                        ich habe kaum Ahnung von Haskell, mit echt funktionalen Sprachen habe ich mich noch nicht wirklich herumgeschlagen.

                        Ich sehe dies: Das Haskell f1 ist etwas ganz anderes als das Perl- (oder JavaScript-) f1. In Haskell wird mit f1 ein Alias in Prefix-Form für die existierende Infix-Funktion "Multipliziere" gebildet, weiter nichts.

                        f3 und f5 werden durch das automatische Currying von Haskell aus f1 abgeleitet; aber dafür hättest Du f1 gar nicht gebraucht, man kann auch Infix-Funktionen direkt zu Curry verarbeiten (falls ich das Haskell-Wiki richtig lese...)

                        Das Perl-f1 (und das JavaScript-f1 des OP) führt dagegen handgemachtes Currying für eine fest vorgegebene Funktion aus. Bei Perl kommt noch das manuelle Beschaffen der Parameter hinzu. Das ist als Einstieg in die Idee "Currying" akzeptabel, aber grundsätzlich nicht gut, es verletzt Separation of Concerns, weil es die Currying-Operation mit der gecurryten Funktion vermengt.

                        Ein besseres JavaScript-Äquivalent zu deiner eleganten Haskell-Curryspeise sähe wohl so aus:

                        function curry2(f, x) {
                           return y => f(x,y);
                        }
                        
                        let muli = (x,y) => x*y;
                        let f3 = curry2(muli, 3);
                        let f5 = curry2(muli, 5);
                        

                        Möglicherweise lässt sich das auch in Perl formulieren; ich weiß nicht, ob in Perl subs als Parameter übergeben werden können.

                        Der Vergleich ist aber auf jeden Fall unfair; natürlich ist Currying in einer funktionalen Sprache viel eleganter als in Sprachen, die dafür keinen fertigen Mechanismus mitbringen.

                        Rolf

                        --
                        sumpsi - posui - clusi
                        1. ich habe kaum Ahnung von Haskell, mit echt funktionalen Sprachen habe ich mich noch nicht wirklich herumgeschlagen.

                          Dann freu ich mich, dass es mir schon mal gelungen ist, dich für eine Haskell-Diskussion zu begeistern.

                          Ich sehe dies: Das Haskell f1 ist etwas ganz anderes als das Perl- (oder JavaScript-) f1. In Haskell wird mit f1 ein Alias in Prefix-Form für die existierende Infix-Funktion "Multipliziere" gebildet, weiter nichts.

                          Genau richtig.

                          f3 und f5 werden durch das automatische Currying von Haskell aus f1 abgeleitet; aber dafür hättest Du f1 gar nicht gebraucht, man kann auch Infix-Funktionen direkt zu Curry verarbeiten (falls ich das Haskell-Wiki richtig lese...)

                          Auch richtig, man hätte f3 und f4 auch so definieren können:

                          f3 = (3 *)
                          f5 = (5 *)
                          

                          Das habe ich an der Stelle nicht gemacht, weil ich versucht habe das originale Beispiel möglichst zu erhalten. Hätte ich überall vereinfacht, wo es ging, dann wäre nur main = print "33 35" übrig geblieben.

                          Mit Currying hat das an der Stelle aber nichts zu tun, ich zeige dir gleich an deinem JavaScript-Beispiel auch warum. In Haskell sind alle Funktionen ausnahmslos einstellig, jede Funktion akzeptiert genau einen Parameter. Mehrstellige Funktionen werden über Funktionen ausgedrückt, die Funktionen zurück geben. Currying in Haskell bedeutet, aus einer Funktion, die ein Tupel als einzigen Parameter hat, eine Funktionskette zu machen, die die Tupel-Komponenten nacheinander entgegennimmt. In JavaScript dagegen gibt es echte mehrstellige Funktionen, aber keinen Tupel-Typen. Currying in JavaScript meint deshalb aus einer mehrstelligen Funktion eine Kette von einstelligen Funktionen zu machen. Ein feiner aber wichtiger Unterschied.

                          Ein besseres JavaScript-Äquivalent zu deiner eleganten Haskell-Curryspeise sähe wohl so aus:

                          function curry2(f, x) {
                             return y => f(x,y);
                          }
                          
                          let muli = (x,y) => x*y;
                          let f3 = curry2(muli, 3);
                          let f5 = curry2(muli, 5);
                          

                          Das ist ein hervorragendes Beispiel, und jetzt kommt die versprochene Auflösung, warum mein Haskell Programm nichts mit Currying zu tun hat. Als Vorbereitung lass mich die curry2-Funktion noch etwas anders aufschreiben, nur damit ich f1 wieder ins Spiel bringen kann, weil ich es später brauchen werde.

                          let curry2 => f => x => y => f(x,y);
                          let muli = (x,y) => x*y;
                          let f1 = curry2(muli);
                          let f3 = f1(3);
                          let f5 = f2(5);
                          

                          Das Haskell-Pendant dazu sieht so aus:

                          curry2 f x y = f (x,y)
                          muli (x,y) = x * y
                          f1 = curry2 muli
                          f2 = f1 3
                          f3 = f1 5
                          

                          Die muli Funktion ist ähnlich zur Standard-Muliplikation, allerdings bekommt sie ihre Operanden nich nacheinander übergeben, sondern zusammengefasst in einem Tupel. Das klingt fast wie Currying, nur andersrum. Die Operation, kann man ebenso herausfaktorisieren, wie du es eben mit der Curry-Funktion gemacht hast. Dieses Andersrum-Curry trägt den passenden Namen uncurry.

                          curry2   f  x y  = f (x,y)
                          uncurry2 f (x,y) = f  x y
                          muli = uncurry2 (*)
                          f1 = curry2 muli
                          

                          Wenn man jetzt die rechte Seite von muli in die Definition von f1 einsetzt, dann kommt da folgendes raus:

                          f1 = curry2 (uncurry2 (*))
                          

                          curry2 ist die Umkehrfunktion von uncurry2, deswegen kann man sich das auch direkt sparen, und landet wieder bei

                          f1 = (*)
                          

                          badum tsss

                          Möglicherweise lässt sich das auch in Perl formulieren; ich weiß nicht, ob in Perl subs als Parameter übergeben werden können.

                          Der Vergleich ist aber auf jeden Fall unfair; natürlich ist Currying in einer funktionalen Sprache viel eleganter als in Sprachen, die dafür keinen fertigen Mechanismus mitbringen.

                          Ich hoffe ich konnte demonstrieren, dass Currying hier in Haskell keinen Vorteil bringt, weil es nicht benutzt wird. Das Handicap von JavaScript ist in diesem Fall, dass der Multiplikations-Operator keine richtige Funktion ist, sondern ein Sprachkonstrukt.

                          1. Hallo 1unitedpower,

                            da muss ich jetzt erstmal eine Weile drüber nachdenken. Und mir darüber klar werden, ob ich mir die Kopfschmerzen wirklich antun will 😂.

                            Immerhin habe ich eins schon kapiert: hier ist ein > zuviel 😉.

                            let curry2 => f => x => y => f(x,y);

                            Und ich habe wohl die Begriffe "Currying" und "Partieller Anwendung" durcheinandergeworfen. Meine Curry2-Funktion führt ja offenbar kein Currying durch, sondern stellt die partielle Anwendung einer zweistelligen Funktion mit einem Parameter dar.

                            Ob deine curry2/uncurry2 Funktionen sich auf der Ebene des generierten Codes tatsächlich aufheben (sprich: Ob Haskell das erkennt und wegoptimiert), das wäre mal eine interessante Frage.

                            Rolf

                            --
                            sumpsi - posui - clusi
                            1. da muss ich jetzt erstmal eine Weile drüber nachdenken. Und mir darüber klar werden, ob ich mir die Kopfschmerzen wirklich antun will 😂.

                              Die Kopfschmerzen gehen auf meine Kappe, weil ich ein miserabler Erklärbär bin - sorry dafür. Ich glaube trotzdem, dass dir Haskell gefallen würde, deiner Aktivität in den Mathe-Threads zu Folge, bist du auf jedenfall Mathematik-begeistert - und die Mathematik hinter Haskell ist äußerst elegant und spannend.

                              Immerhin habe ich eins schon kapiert: hier ist ein > zuviel 😉.

                              Gut gesehen.

                              Und ich habe wohl die Begriffe "Currying" und "Partieller Anwendung" durcheinandergeworfen. Meine Curry2-Funktion führt ja offenbar kein Currying durch, sondern stellt die partielle Anwendung einer zweistelligen Funktion mit einem Parameter dar.

                              Ob deine curry2/uncurry2 Funktionen sich auf der Ebene des generierten Codes tatsächlich aufheben (sprich: Ob Haskell das erkennt und wegoptimiert), das wäre mal eine interessante Frage.

                              Da bin ich mir selber nicht sicher. Was man auf jeden Fall machen könnte, ist dem Haskell-Compiler die Optimierung bekannt zu machen, dazu kann man Termersetzungsrelgen angeben.

                              {-# RULES
                                "curry2/uncurry2" forall f . curry2 (uncurry2 f) = f
                                "uncurry2/curry2" forall f . uncurry2 (curry2 f) = f
                              #-}
                              

                              Hier werden zwei Optimierungs-Regeln definiert, die erste Regel trägt den Namen "curry2/uncurry2", der Name dient lediglich Entwickler(innen) als Hilfe fürs Debugging und taucht im kompilierten Code nicht mehr auf. Die Regel drückt aus, dass wann immer ein Term der Form curry2 (uncurry2 f) gefunden wird, darf dieser Term durch f ersetzt werden und umgekehrt. Das f muss nicht f heißen, sondern kann ein beliebiger Term sein, deshalb ist f all-quantifiziert.

                              Eleganter wäre es natürlich den Bloat von vornerein zu vermeiden.

                        2. hi @Rolf B

                          Möglicherweise lässt sich das auch in Perl formulieren; ich weiß nicht, ob in Perl subs als Parameter übergeben werden können.

                          Aber sicher doch. Mit dem Schlüsselwort $ref=sub{} wird ja eine Referenz erzeugt und das ist ein ganz normaler Scalar. Gewöhnlich werden Callbackfunktionen als Referenz übergeben.

                          Ansonsten ist so etwas

                          f3 = f1(3);
                          

                          eine der gräßlichsten Vorgegehensweisen die leider auch in Perl Einzug gehalten haben. Sowas ist nämlich ganz schlecht fürs Debuggen, weil es gar keine Funktion gibt in die man schauen könnte um zu gucken was die eigentlich macht. Gute Frage 800 Zeilen weiter unten: was macht eigentlich f3??? Codereferenzen sind also mit Vorsicht einzusetzen wobei man das auch immer sehr gut dokumentieren sollte.

                          Und wenn man schon mit CODE Referenzen arbeitet dann wenigstens so:

                          my $browse = sub{
                             my $self = shift;
                             # weiterer CODE
                          };
                          

                          also daß man die Funktion namentlich finden und den Code in Augenschein nehmen kann. Und wenn Coderefs schon Konstanten sein sollen, ja warum dann nicht gleich richtige Konstanten definieren!? Daß PI die Zahl 3.14 ist braucht keinen Kommentar.

                          Und trotzdem sind auch Konstanten mindestens genauso schlecht wie globle Vars, eben weil man nicht einfach einen Dump mit allen Konstanten ausgeben kann. D.h., wer wissen will, welche Konstanten IO::File deklariert, muß wohl oder übel in die POD schauen. Immerhin sind Konstanten wenigstens konstant 😉

                          MfG

      3. hi,

        nutze die Möglichkeiten die Perl5 bezüglich dyn. Scope bietet:

        use strict;
        use warnings;
        
        local $, = "\n";
        sub multipli{ 
            my $x = shift;
            sub{
                my $y = shift;
                $x * $y;
            }
        }
        
        # CODE Ref in our Scope
        # es wird nur eine Ref benötigt
        our $m = multipli(3);
        
        print $m->(11), "\n";    # 33
        
        # dynamischer Scope in Block
        {
            local $m = multipli(5);
            print $m->(7), "\n"; # 35
        }
        
        # $m ist unverändert
        print $m->(11), "\n";    # 33
        

        So genügt es, mur eine Referenz im Scope our anzulegen. Nachdem der Block {} verlassen wurde, hat $m wieder seinen ursprünglichen Wert was mit local $m erreicht wurde.

        MfG

  5. Moin,

    wenn es mehre Möglichkeiten zur Schreibweise gibt, kann man über Geschmack streiten. Unstrittig jedoch ist

    function f1(x) {
        return function(y){
            return x * y;
        }
    };
    

    besser lesbar. Das Ergebnis ist dasselbe.

    MfG

    1. Hallo pl,

      Das Ergebnis ist dasselbe.

      Nein, nicht immer.

      Bis demnächst
      Matthias

      --
      Rosen sind rot.
  6. Eine etwas detailiertere Beschreibung des merkwürdigen Pfeils: https://hacks.mozilla.org/2015/06/es6-in-depth-arrow-functions/