Announcement

Collapse
No announcement yet.

JM 4.06: Funktionale Programmierung

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • JM 4.06: Funktionale Programmierung

    Ein paar Anmerkungen zum Artikel von Christian Neumann:

    Neue Programmiersprachen zu lernen um den Horizont zu erweitern, wie im letzten Kapitel empfohlen, ist sicher eine gute Idee.

    In Frage zu stellen ist aber, ob die Umsetzung in einer Programmiersprache wie Java, die dieses Paradigma nicht unterstützt, unbedingt eine gute Idee ist.

    Den im Abstract genannten Vorteil funktionaler Programmiersprachen, kürzer zu sein, ist fast immer auch durch andere Typsysteme verursacht. Wenn diese nicht sowieso dynamisch getypt sind, so ermöglicht es Typinferenz doch mit weniger Deklarationen bei gleicher Typsicherheit auszkommen. Dies hat aber auch Einschränkungen: Scala zum Beispiel kennt keine Interfaces.

    Weitere Vorteile funktionaler Programmiersprachen, etwa die implizite Parallelisierbarkeit durch die Freiheit von Seiteneffekten hat man so in Java nicht.

    Die Verwendung des fremden Programmierparadigmas bringt jedoch auch Nachteil mit sich, auf die der Autor nicht eingeht:

    1) Die vermeintliche Eleganz erkauft sich der Programmierer mit jeder Menge Objektinstanziierungen. Für Applet-Programmierer kann sogar die Vielzahl zu ladender Klassen problematisch sein.
    2) Der Code ist für einen neuen Programmierer, der die Zusatzklassen nicht kennt, schwerer und nicht etwa leichter zu verstehen.
    3) Auch das Debugging dieser Konstrukte ist kein Vergnügen.
    4) Die unkritische Übernahme der funktionalen Terminologie kann für Verwirrung sorgen: Was macht eigentlich reduce? Die Begründung für die Namenswahl sind technische Details, nicht das, was man aus Fachsicht hier machen will.
    5) Auch some und every sind nicht sofort verständlich. Hier läßt sich evtl noch etwas durch eine Umbenennung verbessern.
    6) Dem Problem der Exception-Behandlung stellt sich die vorgestellte Lösung aber sowas von überhaupt nicht. Wer die vorgestellten Klassen benutzen will, muß die Exceptions lokal behandeln und u.U. in unchecked Exceptions verpacken.
    Da schmeckt es schon nach arg unangebrachtem Eigenlob wenn von "fehlerfreiem" "qualitativ hochwertigem Programmcode" die Rede ist. Die Beispielimplementierungen lösen nur die allereinfachsten Probleme und sind so nicht alltagstauglich.

    Trotz allem, es kann durchaus Sinn machen. Wer sich durch JDBC-Code bewegt, dem wird häufig schon die Abfolge "Query ausführen", "Abklappern des ResultSet" und häufig fehlerhafte Ausnahmebehandlung aufgefallen sein. Dies schreit nach Abstraktion und man wundert sich, warum entsprechendes nicht von Anfang an in JDBC enthalten ist. Auch hier setzt Java mit dem Problem der Exception-Behandlung allerdings Grenzen.

    Eines noch: Die Verwendung von varargs um einen(!) Parameter optional zu machen und falsche Verwendung erst zur Laufzeit als per RuntimeException zu melden, verdient eigentlich die Erwähnung auf www.thedailywtf.com.

    Summa summarum: Wer funktional programmieren will, sollte eine funktionale Programmiersprache verwenden.

  • #2
    Naja, eigentlich diskutiere ich nur mit Leuten, die ihren Namen nennen, aber hier ausnahmsweise mein Kommentar zu den Nachteilen:

    (1) Die Anzahl der Objekte, die man hier erzeugt, hält sich sicherlich in Grenzen. Viel schlimmer sind beispielsweise String-Konkatenationen mit dem „+“-Operator, denn hier werden genauso neue Objekte erzeugt – was wahrscheinlich viel öfters (unbemerkt) vorkommt, als die Benutzung einer funktionalen Hilfsmethode.

    (2) Der Code ist für einen neuen Programmierer sicherlich schon nach kurzer Zeit ebenso durchschaubar, wie die statischen Hilfsmethoden im JDK (vgl. Artikel). Der Benutzer muss nicht komplexe Algorithmen in FP implementieren, sondern diese Hilfsmethoden nur verwenden. Außerdem: Wenn ich was Neues lerne (anderes Beispiel: die Java 5 Generics), dann braucht man eben ein wenig Zeit um das zu verstehen.

    (3) Das Debugging ist auch kein Problem. Die FP-Methoden (wie map, reduce) sind fehlerfrei. Schlimme Sachen in den Prädikaten/Funktionen sind m.E. nicht zu erwarten.

    (4) Was macht eigentlich reduce? Das weiß man, wenn man diese Methode einmal benutzt hat. Eine unverständliche Terminologie kann Verwirrung auch bei Methoden von ganz normalen Anwendungsklassen verursachen – und das hat mit funktionaler Programmierung rein gar nichts zu tun. Oder: Was machen die JDK-Methoden? Da schaut man auch in die Dokumentation. Im Artikel war leider kein Platz mehr für die ausführliche Java-Doc.

    (5) Muss man nur ins Deutsche übersetzen: „einige“ und/oder „alle“ Elemente der Liste entsprechen einem Prädikat (sind also wahr).

    „Die Beispielimplementierungen lösen nur die allereinfachsten Probleme“:

    Die Beispiele sind m.E. gut gewählt. Klar gibt es komplexere Probleme, aber das macht überhaupt keinen Sinn, einen weiteren Schwierigkeitsgrad in den Artikel zu bringen. Der Artikel ist nicht für die größten Experten gedacht, sondern für „alle“ Java-Programmierer. „Jeder“ soll die Möglichkeit haben, den Artikel zu verstehen.

    Zum optionalen Parameter und der Fehlerbehandlung:

    In der Java-Welt gibt es keine einhellige Meinung, wann man geprüfte und wann man ungeprüfte Ausnahmen verwendet. In der Literatur werden manchmal geprüfte Ausnahmen verboten, manchmal aber auch ungeprüfte. Würde man eine geprüfte Ausnahme nehmen, dann hat der Benutzer höchstwahrscheinlich wenige Ideen, wie er diese Ausnahme zu behandeln hat. Das könnte wiederum zu einem leeren catch-Block führen. Oder er reicht die Ausnahme solange weiter, bis sie sich im ganzen Softwaresystem verteilt hat. Es ist sicherlich nicht gut, wenn sich eine geprüfte Ausnahme von reduce verteilt, deswegen wird diese Ausnahme an Ort und Stelle in der abstrakten Klassen FP geworfen. Des Weiteren zähle ich den möglichen Fehler von reduce zu einem abnormen Ergebnis. Abnorme Ergebnisse werden als ungeprüfte Ausnahmen gemeldet (vgl. einschlägige Literatur). Abnorme Ergebnisse sind sehr selten. Selbst die Programmierer mit weniger Erfahrung werden kaum auf die Idee kommen, reduce mit mehr Parametern aufzurufen.

    In anderen Programmiersprachen gibt es ganz und gar nur ungeprüfte Ausnahmen.

    „Wer funktional programmieren will, sollte eine funktionale Programmiersprache verwenden“:

    Ich will nicht funktional programmieren, sondern mit Java. Den funktionalen Programmierstil sehe ich als hilfreiche Ergänzung der übrigen Möglichkeiten um den Programmcode zu verbessern.

    Christian Neuman

    Comment


    • #3
      Nun, bei den meisten Punkten läuft es auf ein "Agree to disagree" hinaus.

      Ich reiche aber noch folgenden Punkt nach:

      Im Artikel wird eine alternative map - Implementierung vorgestellt, zu Recht aber davor gewarnt, daß diese aufgrund von Seiteneffekten zu Problemen führen kann.

      Nun ist die Implementierung selber korrekt und frei von Seiteneffekten. Das Problem ist, daß die Verwendung an anderer Stelle Seiteneffekte erlaubt, die zu Problemen führen können.

      Hier ist es also Java, das leider Seiteneffekte nicht verhindert und somit elegante funktionale Implementierungen nicht ermöglicht.
      Auch hier also die Diskrepanz zwischen der funktionalen Welt und der Umsetzung in eine imperative Programmiersprache, die zu Problemen führen kann.

      Zu den Diskussionspunkten:

      (3) Das Debugging ist ein Problem (wenn auch ein kleineres). Bei einem Step-by-Step-Debugging wird der Entwickler auch immer wieder durch die Implementierung der foreach() - Methode laufen, obwohl er nur seinen eigenen Code debuggen will. Dies ist ein Unterschied zum Debuggen einer for- Schleife

      Zu den Exceptions:
      Java sieht die Unterscheidung zwischen checked und unchecked Exceptions vor, somit stehe ich der Literatur, die eine von beiden Varianten verbieten will, skeptisch gegenüber.

      Checked Exceptions haben dort ihren Sinn, wo das Auftreten der Ausnahme nicht die gesamte Anwendung stoppen soll.

      Leider sehe ich hier bei den Java-Generics noch Nachbesserungsbedarf. Generics, die dazu führen, daß checked Exceptions gefangen und in unchecked Exceptions übersetzt werden müssen, sind sicher nicht der Weisheit letzter Schluß

      Comment


      • #4
        Zu den Seiteneffekten:

        Seiteneffekte sind prinzipiell möglich, aber wenn man die bekannten Probleme berücksichtigt, dann sollte man in Java auch in keine Fallgrube stolpern. Prinzipielle Fehlermöglichkeiten gibt es wohl in allen Programmiersprachen.

        Zu Debugging (auch wenn es plötzlich nur noch ein kleines Problem sein soll):

        Ich glaube, dass Debugging mit FP leichter ist, da man immer auf dem aktuellen Stackframe den kompletten Zustand sieht. Vielleicht noch etwas einfacher gehalten: Dass ich jedes Mal durch die foreach() - Methode laufen muss, ist eine völlig normale und alltägliche Situation in Java (das könnte man eigentlich wissen). Hier vergleichbare Beispiele mit statischen Hilfsmethoden aus dem JDK oder anderen Frameworks:

        Logger.info(…)
        Collections.reverse(…)
        Array.get(…)
        Arrays.sort(…)
        FP.forEach(…)

        Wer diese Algorithmen jedes Mal neu programmiert, der kann eben Fehler machen oder auch nicht. Lagert man solche Algorithmen an eine Stelle aus, dann kann man garantieren, dass der Algorithmus fehlerfrei ist (vgl. Artikel). Außerdem sind diese Sachen viel einfacher vom Softwareingenieur zu benutzen. Und der Programmcode wird kürzer und klarer.

        Zu Ausnahmen in Java:

        Geprüfte Ausnahmen sollte man nicht verbieten, sondern vernünftig damit umgehen. Bei geprüften Ausnahmen wird der Code des Benutzers der funktionalen Hilfsmethoden von zahlreichen try/catch Blöcken durchzogen sein. Dies führt zur schlechteren Lesbarkeit des Codes und außerdem stellt sich dann die Frage, ob die Ausnahmebehandlung des Benutzers vollständig sein wird.

        Es wird wahrscheinlich kaum passieren, dass jemand reduce mehr Parameter spendiert. Diese abnormen Ergebnisse zeichnen sich dadurch aus, dass man lokal nicht vernünftig reagieren kann. Deswegen macht es hier auch ganz wenig Sinn eine geprüfte Ausnahme zu verwenden und in eine ungeprüfte Ausnahme umzuwandeln. Diese Mehrarbeit ist überflüssig, wenn man eben die ungeprüfte Ausnahme gleich in FP wirft.

        Die Frage, wer bei Java-Generics noch Nachbesserungsbedarf sieht, hat mit dem Artikel rein gar nichts zu tun. Sorry.

        Christian Neuman

        Comment


        • #5
          Also ich finde die Idee an und für sich recht sexy (und fand den Artikel deshalb recht interessant). Hatte auch schon versucht, die SQL Aggregatfunktionen
          <pre>
          SUM, MIN, MAX, AVG, COUNT
          </pre>
          auf eine List<T extends Number> anzuwenden; oder mit einem einfachen Prädikat
          <pre>
          puclic interface Predicate<T>{
          boolean test(T t);
          }
          </pre>
          zu filtern, usw. usf. Habe aber nach einigen Versuchen irgendwie den Eindruck bekommen, dass der Code nicht wirklich kürzer wird. Ist eben einfach so, dass
          <pre>
          for(Integer x : liste){ sum += x; }
          </pre>
          auch sehr gut lesbar ist. Von daher hab ich jetzt wieder eher gemischte Gefühle. Ist halt nicht so wie in Lisp/Scheme, wo gar keine besondere Notation nötig ist damit ein Function<T,V> auf jeden Eintrag einer Liste anzuwenden ist...

          BTW

          Unglaublich fand ich Seite 24 Listing 3
          <pre>
          ToCustomer
          &lt;T extends String&gt;
          </pre>
          Auch im qualitativ hochwertigen Software-Engeneering und der modernen Softwarearchitektur und dem professionellen Programmieren ist String immer noch final

          Comment


          • #6
            Können die beiden Künstler &quot;Entwurfsmuster&quot; und &quot;Foo Bar&quot; wirklich Java oder tun sie nur so?

            Sorry &quot;Foo Bar&quot;, aber vielleicht sollte man sich erstmal in Java Generics einarbeiten, bevor man solche unqualifizierten Kommentare von sich gibt!

            Das String final ist, weiß wohl jeder!

            ToCustomer&lt;T extends String&gt; garantiert uns, dass wir ToCustomer nur mit einem String aufrufen können - die Syntax dafür, also für die Java Generics habe ich mir nicht ausgedacht. Natürlich kann man String nicht erweitern, aber bei den Java Generics schaut die Syntax eben so aus, wenn man ein Template verwenden will.

            Vielleicht hilft es, wenn man erst mal nachdenkt, bevor man postet – oder vielleicht tut es auch ein Anfänger-Kurs für Java Generics.

            Ah ja, das Ziel ist nicht nur kürzerer Programmcode, sondern dass man dadurch auch duplizierten Code verhindern kann – und das passt zu qualitativ hochwertigen Programmcode.

            Christian Neumann

            P.S. Soll ich noch erklären was duplizierter Code ist

            Comment


            • #7
              Ich habe den Artikel nur überflogen, die Stelle die Foo Bar anmerkt ist mir aber auch aufgefallen. Was macht denn eine generische Klasse die ich nur mit String parametrisieren kann für einen Sinn? Da kann man doch gleich

              public class ToCustomer implements UnaryFunction<Customer, String>

              schreiben.

              Alwi

              Comment


              • #8
                FP.map(new ToCustomer<String>(), customerNumbers);

                liest sich besser als

                FP.map(new ToCustomer(), customerNumbers);

                oder

                FP.map(new ToString<Customer>(), customers);

                liest sich besser als

                FP.map(new ToString(), customers);

                Wozu das Ganze? Die Beispiele sind bewusst so gewählt, damit man mal sieht, dass Java Generics auch zu sprechendem Programmcode führen können (so wie im Artikel). Mit Deiner/Ihrer Lösung verlieren wir die Information auf Benutzerseite, was da genau transformiert wird, und vor allem wohin. Im Artikel oder den beiden Beispielen oben sieht man, dass Strings (z.B. Kundennummern) in Customer-Objekte transformiert werden, also: ToCustomer<String>. Deswegen macht das Sinn. Ansonsten verlieren wir diese Information und der Code ist nicht mehr ganz so sprechend.

                Aber generell gilt: Jeder kann FP anpassen wie er will, sich neue Schnittstellen ausdenken und/oder andere erweitern. Oder Schnittstellen implementieren wie er will. Viel wichtiger ist doch das ganze Zusammenspiel!

                FP ist übrigens sehr alltagstauglich: Das man eine Liste nach einer Bedingung filtern muss, kommt wohl öfters vor (z.B. alle Mietwagen filtern, die noch verfügbar sind). Der Vorteil liegt klar auf der Hand: Muss man später erneut filtern, und sei es eine ganz andere Bedingung, dann muss man sich überhaupt keine Gedanken um den Algorithmus machen, sondern schreibt immer nur diese eine Zeile:

                FP.filter(new IsAvailable<RentalCar>, cars);

                Hier sieht man den Vorteil des sprechenden Programmcodes sogar noch besser: Gib mir alle Leihwagen, die noch verfügbar sind! Besser geht es wohl nicht.

                Gruß
                C

                Comment


                • #9
                  das ist doch jetzt wirklich die Höhe
                  <br>
                  die Klasse (beziehungsweise "Funktion") ToCustomer muss ja extra geschrieben werden, und so wie das gemacht wurde MUSS String der Typparameter sein!
                  <br>
                  Und wegen des Erasure kann man keine zweite Klasse mit diesem Namen haben, also warum dann nicht gleich
                  <pre>
                  class customerToString implements UnaryFunction<Customer,String>
                  </pre>
                  Hä? Ist das nicht besser lesbar?? Macht das Sinn??

                  Comment


                  • #10
                    Tja, auch wenn ich "Künstler" laut Hr. Neumann nur so tue, als ob ich programmieren kann, hier noch ein Verbesserungsvorschlag:

                    Das Interface CharSequence gibt es seit Java1.4 und ist in vielen Fällen die bessere Alternative zu String.
                    Insbesondere kann man sich häufig das stringBuffer.toString() ersparen.

                    Und hier hätte es noch eine Rechtfertigung für den Generic abgegeben, wenn man eben
                    T extends CharSequence

                    statt
                    T extends String

                    geschrieben hätte

                    Comment


                    • #11
                      Sorry an die beiden Künstler: Ihr habt entweder rein gar nichts verstanden oder Ihr wollt das nicht verstehen!

                      Ich bin nicht bereit, die Sachen zehnmal zu erklären – offensichtlich fehlt das Verständnis für einfachste Zusammenhänge.

                      Deswegen werde ich diesen Thread nicht weiter kommentieren!

                      Hier noch ein Tipp: Wenn Ihr – also nur „Entwurfmuster“ und „Foo Bar“ - der Meinung seid, hier so schlaue Kommentare abgeben zu müssen, dann macht doch das einfach unter euerem Namen – der Chef/Vorgesetzte oder die Firma freut sich garantiert.

                      P.S. Wenn man euere Kommentare mal wirklich ernsthaft liest, dann sieht man sogar, dass Ihr euch teilweise selbst widersprecht. Mehr muss man gar nicht mehr sagen.

                      Ende

                      Comment


                      • #12
                        &lt;T extends String&gt;

                        ist SINNLOS

                        Ende

                        Comment


                        • #13
                          Wie gesagt: Ein Generics-Grundkurs könnte helfen..

                          Comment


                          • #14
                            also dann nehme ich den hier und frag den Fachmann
                            <i>
                            FP.map(new ToCustomer&lt;String&gt;(), customerNumbers);
                            liest sich besser als
                            FP.map(new ToCustomer(), customerNumbers);
                            oder
                            FP.map(new ToString&lt;Customer&gt;(), customers);
                            liest sich besser als
                            FP.map(new ToString(), customers);
                            </i>
                            ich VERSTEH DAS EINFACH NICHT

                            was ich oben sagen wollte (mit Tippfehler), ist folgendes:

                            WEIL die Klasse mit der Implementierung ja extra geschrieben werden muss, kann man die tolle Zusatzinformation ja gleich in den <b>Namen</b> der Klasse mit aufnehmen
                            <pre>
                            class StringToCustomer implements UnaryFunction&lt;Customer,String&gt;
                            </pre>
                            dann würde ja einfach dastehen
                            <pre>
                            FP.map(new StringToCustomer(), customers);
                            </pre>
                            und das ist IMHO genauso gut lesbar und kommt ohne das Verwirrende T extends String aus. Ich schnalls einfach nicht, bin wohl zu blöd für Generics? Oder übersehe ich was?

                            Comment


                            • #15
                              Ach ja
                              <i>
                              Der Vorteil liegt klar auf der Hand: Muss man später erneut filtern, und sei es eine ganz andere Bedingung, dann muss man sich überhaupt keine Gedanken um den Algorithmus machen, sondern schreibt immer nur diese eine Zeile:

                              <b>FP.filter(new IsAvailable&lt;RentalCar&gt;, cars); </b>

                              Hier sieht man den Vorteil des sprechenden Programmcodes sogar noch besser: Gib mir alle Leihwagen, die noch verfügbar sind! Besser geht es wohl nicht.
                              </i>
                              Auch das leuchtet mir jetzt nicht unmittelbar ein, mit dem neuen for-Konstrukt würde ich hinschreiben (direkt)
                              <pre>
                              for(Car car: cars){
                              if(car.isAvailable()){
                              ....sth
                              }
                              }
                              </pre>
                              und das ist doch nicht "schlechter" als der FP Ansatz, wenn auch nicht ganz so flexibel? Und im Quellcode ist alles schön an einer Stelle usw. usf.

                              Wie schon gesagt: ich finde die FP Idee sehr sexy, aber IMHO führt das nicht unbedingt zu weniger oder klarerem Code. Bin also nicht so ganz überzeugt ("over engeneering"

                              Comment

                              Working...
                              X