Kann mir hier jemand Sinn und Verwendung von https://de.m.wikipedia.org/wiki/Monade_(Informatik) an einem verständlichen praktischen Beispiel erklären?
Ich fand diesen Artikel sehr hilfreich: http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html?m=1 mfg mf
Achim M. schrieb: > Ich fand diesen Artikel sehr hilfreich Hallo Achim, solche schnell gegoogelten Artikel sind für einen unstudierten praktischen Programmierer wie mich nicht unbedingt erhellender. Wenn Du es aber verstanden hast kannst Du mir bestimmt eine Anwendung anhand eines praktischen Beispiels erläutern? Wobei hilft diese Konstruktion genau?
Gast
#7141053
Ron T. schrieb: > solche schnell gegoogelten Artikel Anstatt Achim schnelles Googlen zu unterstellen, wäre es zielführender, dar zu stellen, wo du mit deinem Verstenen scheiterst und eine gezielte und konkrete Rückfrage zu schreiben. Es hilft, daran zu denken, dass du der Informations Suchende bist. Die hilfsbereiten Personen hier sind nicht Lehrbeauftragte, also Personen mit bestehendem Auftrag, auf den man sich berufen könnte, sondern freiwillig Helfende.
Mona Lisa schrieb: > Anstatt Achim schnelles Googlen zu unterstellen Hier wird nichts unterstellt sondern nur zum Ausdruck gebracht daß sich im Netz meist nur dergestalt "Erklärungen" finden lassen- deshalb ja meine Frage Mona Lisa schrieb: > wäre es zielführender, > dar zu stellen, wo du mit deinem Verstenen scheiterst die doch recht eindeutig formuliert ist! Was kannst Du denn dazu beitragen?
Gast
#7141335
Tja, meinem Hinweis, deinen sprachlichen Ausdruck mal zu reflektieren, konntest du nichts abgewinnen. Für die erste Quelle, die dir genannt wurde, hast du dich nicht bedankt. Auf die versuchte Hilfestellung von Achim bist du inhaltlich nicht eingegangen und hast stattdessen mit Nörglei reagiert. Die Frage, wo du konkret mit deinem Verständnis ausgestiegen bist, ist immer noch unbeantwortet geblieben. Da sehe ich jetzt keine Veranlassung, dir inhaltlich zu antworten. Folglich steht deine Frage, was ich zur Beantwortung deine Frage beizutragen hätten, noch gar nicht zur Diskussion.
Gast
#7141398
Ron T. schrieb: > Kann mir hier jemand Sinn und Verwendung von > https://de.m.wikipedia.org/wiki/Monade_(Informatik) an einem > verständlichen praktischen Beispiel erklären? Monaden sind eine Abstraktion. Ob man diese Abstraktion nun betrachtet, wenn man etwas umsetzt, was man als Monade abstrahieren kann, ist eine andere Frage. In Haskell werden Abläufe von Operationen mit Nebeneffekten als Monade abstrahiert, in C beschäftigt man sich im Allgemeinen nicht damit. C:
1 | |
2 | |
3 | |
4 | |
5 | |
Haskell:
1 | |
Gast
#7141413
Oder noch ein anderes Beispiel:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
Dieser Haskell-Code bildet paarweise die Produkte der ungeraden Zahlen von 1 bis 10 und nutzt dabei die Tatsache, dass Listen ebenfalls Monaden sind. Würde ich das produktiv so schreiben? Nein. Der Punkt, den ich machen möchte ist, dass du (sofern du programmierst) schon ziemlich sicher Konstrukte angewendet hast, die man als Monade beschreiben kann: nämlich sequenzielle Berechnungen, bei deren einfach gesagt das Ergebnis einer Berechnung von dem der vorhergehenden abhängt. Du hast es nur vermutlich nie so genannt. Ein großer Vorteil davon, diese Eigenschaft zu abstrahieren, ist, dass man zB die Unterscheidung zwischen synchronen und asynchronen Funktionen in anderen Programmiersprachen nicht braucht, weil man über beide Arten abstrahieren kann. Das Konzept einer Monade ist (imho) ziemlich abstrakt und somit brauchen die Beispiele auch etwas, um zu sacken (bei der Liste hat es bei mir schon eine Weile gedauert).
Gast
#7141475
Ron T. schrieb: > Kann mir hier jemand Sinn und Verwendung von > https://de.m.wikipedia.org/wiki/Monade_(Informatik) an einem > verständlichen praktischen Beispiel erklären? Welche "Stand" hast Du denn so als "unstudierten praktischen Programmierer" erreicht? In welcher Sprache programmierst Du? Eine "Monade" ist halt schon etwas sehr Spezielles. Wenn Du noch nie Kontakt zu seiteneffektfreien Funktionen, Typparametern etc. hattest, ist es halt schwierig und Du musst Dich mit allgemeinen Antworten abfinden. Wenn Du einen Biochemiker nach der Funktion eines bestimmten Protein fragst, kann der einem Laien auch nur eine Trivial-Antwort geben. Und so ist bei Monaden: Es ist ein bestimmter Mechanismus um Berechnungen zu verknüpfen.
Gast
#7141515
Ron T. schrieb: > solche schnell gegoogelten Artikel Ich glaube nicht, dass das "schnell ergoogelt" wurde. Der Link ist auch eine Referenz in dem Buch "Haskell-Intensivkurs". Schnell zusammengesucht wäre das hier: https://stackoverflow.com/questions/44965/what-is-a-monad? https://stackoverflow.com/questions/46633927/haskell-monad-flow-understanding https://stackoverflow.com/questions/1105765/generating-fibonacci-numbers-in-haskell https://stackoverflow.com/questions/40553192/understanding-monadic-fibonacci https://wiki.haskell.org/The_Fibonacci_sequence http://www.betoerend.de/dasLandHinterDemEndeDesSinns/lambda/welcome.html Monaden könnte man auch unter der Überschrift "imperative Haskellprogrammierung" einsortieren. Problematisch ist, dass jede Funktion in Haskell immer nur einen Wert ausgibt. Aber man kann bestimmte sequentielle Verläufe wie bei den Fibonaccis auch ganz gut ohne Monade hinbekommen. Monaden helfen u.a. dabei, wenn das Arbeiten mit sequentiellen Verläufen zu unübersichtlich wird, etwas mehr Ordnung in den Code hineinzubekommen, bzw. den Umgang mit solchen Problemen zu erleichtern. Prinzipiell machen das die List Comprehensions auch - aber auch diese, wie das Arbeiten damit, wollen gelernt, und eingeübt werden. Wirklich einfach sind die Monaden weder erlernbar, noch vermittelbar, weil sie halt spezielle Abstraktionen sind - und bei denen hieß es schon bei C Programmierung, dass diese sich nach 5 Jahren (in der Anwendung) etwa auszahlen. Haskell ist noch einen Nummer abstrakter, da sollte man vielleicht 7 Jahre ansetzten. Bei der Programmierung generell wird viel in der Praxis gelernt, also viel Üben (auch ohne alles gleich zu verstehen) - weniger durch Lesen allein.
Hallo, in einer rein funktionalen Sprache haben Funktionen keine Nebeneffekte, d.h. bei gleichem Input ist auch der Output immer gleich. Die Ausgabe ist z.B. ein Nebeneffekt und nicht das Ergebnis einer Funktion. Monaden sind dazu da, nicht-funktional mit Nebeneffekten (z.B. I/O) zu programmieren. Besser ist es evtl. hier erklärt: https://www.haskell.org/tutorial/monads.html
Gast
#7141579
Alexander S. schrieb: > Hallo, > > in einer rein funktionalen Sprache haben Funktionen keine Nebeneffekte, > d.h. bei gleichem Input ist auch der Output immer gleich. Die Ausgabe > ist z.B. ein Nebeneffekt und nicht das Ergebnis einer Funktion. Monaden > sind dazu da, nicht-funktional mit Nebeneffekten (z.B. I/O) zu > programmieren. > Besser ist es evtl. hier erklärt: > https://www.haskell.org/tutorial/monads.html Sry für das nitpicking: aber ist main etwa keine Funktion? Ich würde es eher so formulieren, dass Monaden in Haskell dazu genutzt werden, die Existenz von Seiteneffekten in den Typ der Funktion mit aufzunehmen. (Dinge in den Typ mit aufzunehmen ist so eine Obsession von Haskell-Programmierer:innen). So ist sicher, dass eine Funktion, die nicht (auch nicht versteckt) IO in ihrer Signatur hat, seiteneffektsfrei. Übrigens hat auch nicht jede Funktion mit Monaden Nebeneffekte: List ist oben schon als Beispiel erwähnt oder Reader wäre ein weiteres. Es gibt Ansätze, die das noch weitertreiben und sogar mit aufnehmen, welche Effekte genutzt werden.
Gast
#7141681
Eins N00B schrieb: > Ich würde es eher so formulieren, dass Monaden in Haskell dazu genutzt > werden, die Existenz von Seiteneffekten in den Typ der Funktion mit > aufzunehmen. Richtig. Dass IO in Haskell mit Monaden implementiert ist, ist so gesehen nebensächlich. Monaden haben erst mal überhaupt nichts mit Seiteneffekten zu tun. Die meisten Leute kommen bei IO in Haskell das erste mal mit Monaden in Berührung. Daher die falsche Assoziation Monade = Seiteneffekte. Aber das ist falsch.
Ein anderer Programmierer schrieb: > Die meisten Leute kommen bei IO in Haskell das erste mal mit Monaden in > Berührung Na dann scheint das Thema ja für die übrige Welt weniger von Belang. Vielen Dank für die konstruktiven Antworten.
Eins N00B schrieb: > Ich würde es eher so formulieren, dass Monaden in Haskell dazu genutzt > werden, die Existenz von Seiteneffekten in den Typ der Funktion mit > aufzunehmen. (Dinge in den Typ mit aufzunehmen ist so eine Obsession von > Haskell-Programmierer:innen). Du meinst, als eine Art Kennzeichnung? Das ist nur die halbe Wahrheit. Natürlich ist es praktisch, dass man allein schon am Rückgabetyp einer Funktion erkennen kann, dass diese irgendwo in ihrem Inneren I/O macht. Der eigentliche Grund für die Verwendung der IO-Monade als Rückgabetyp liegt aber darin, dass damit die I/O aus der Funktion ausgelagert wird und diese nebeneffektfrei bleibt, so wie es in einer rein funktionalen Sprache eben sein muss. > Sry für das nitpicking: aber ist main etwa keine Funktion? Das im letzten Absatz Geschriebene gilt natürlich auch für main. Die gesamte I/O findet formal außerhalb von main statt. Man kann sich das rein formal so vorstellen, dass die von main zurückgegebenen IO-Monade sämtliche Informationen enthält, die das Laufzeitsystem benötigt, um die I/O des gesamten Programmablaufs abzuwickeln. Zu diesen Informationen zählen insbesondere die (allgemein formulierten) Abhängigkeiten der Ausgabe von der Eingabe. Damit ist der Inhalt der von main gelieferten I/O-Monade ist für jeden Programmablauf gleich und main (und natürlich auch jede andere Funktion des Programms) nebeneffektfrei. Neben Monaden, wie sie in Haskell verwendet wurden, gibt es noch weitere I/O-Konzepte für rein funktionale Sprachen. Allen gemeinsam ist, dass sie nur in Verbindung mit Lazy Evaluation (also der bedarfsabhängig verzögerten Auswertung von Funktionen) realisierbar sind. Ein anderer Programmierer schrieb: > Monaden haben erst mal überhaupt nichts mit Seiteneffekten zu tun. Die > meisten Leute kommen bei IO in Haskell das erste mal mit Monaden in > Berührung. Das ist logisch, weil ein Programm ohne I/O sinnlos wäre, was sich ja auch im Typ von main (IO ()) widerspiegelt. Die I/O in Haskell war letztendlich auch der Anlass für die Einführung der Monaden. Sie waren primär als ein flexiblerer Ersatz für die zuvor verwendeten I/O-Streams gedacht. Nebenbei hat man erkannt, dass man auch andere Dinge wie Listen, Maybe, Either usw. als Monaden definieren und damit ebenfalls sinnvolle Dinge anstellen kann, so dass man für Monaden gleich eine universell nutzbare Typklasse angelegt hat.
Gast
#7141965
Ron T. schrieb: > Ein anderer Programmierer schrieb: >> Die meisten Leute kommen bei IO in Haskell das erste mal mit Monaden in >> Berührung > > Na dann scheint das Thema ja für die übrige Welt weniger von Belang. > Vielen Dank für die konstruktiven Antworten. Die Antwort hättest du auch deutlich kürzer haben können :D . Yalu X. schrieb: > Eins N00B schrieb: >> Ich würde es eher so formulieren, dass Monaden in Haskell dazu genutzt >> werden, die Existenz von Seiteneffekten in den Typ der Funktion mit >> aufzunehmen. (Dinge in den Typ mit aufzunehmen ist so eine Obsession von >> Haskell-Programmierer:innen). > > Du meinst, als eine Art Kennzeichnung? Nicht nur. Die Signatur bestimmt ja auch die Möglichkeiten oder Einschränkungen der Nutzung. > Das ist nur die halbe Wahrheit. Natürlich ist es praktisch, dass man > allein schon am Rückgabetyp einer Funktion erkennen kann, dass diese > irgendwo in ihrem Inneren I/O macht. > > Der eigentliche Grund für die Verwendung der IO-Monade als Rückgabetyp > liegt aber darin, dass damit die I/O aus der Funktion ausgelagert wird > und diese nebeneffektfrei bleibt, so wie es in einer rein funktionalen > Sprache eben sein muss. Ist das so? Mit etwas schwarzer Magie kann man ja auch jede andere Funktion mit Seiteneffekten versehen, selbst strict evaluierte. Ich hätte jetzt als Hauptgrund vermutet, dass die Sprache sonst keine Ausdrucksmöglichkeit für sequenzielle Abläufe hätte. Bei let-Bindings ist ja zB keine Reihenfolge in deren Auswertung definiert. > >> Sry für das nitpicking: aber ist main etwa keine Funktion? > > Das war eher eine rhetorische Frage 😇 > > Neben Monaden, wie sie in Haskell verwendet wurden, gibt es noch weitere > I/O-Konzepte für rein funktionale Sprachen. Allen gemeinsam ist, dass > sie nur in Verbindung mit Lazy Evaluation (also der bedarfsabhängig > verzögerten Auswertung von Funktionen) realisierbar sind. Idris ist strict by default und nutzt auch Monaden. Warum sind sie nur mit lazy evaluation realisierbar?
Eins N00B schrieb: > Idris ist strict by default und nutzt auch Monaden. Warum sind sie nur > mit lazy evaluation realisierbar? Ich habe mich bisher noch nicht mit Idris beschäftigt, aber das I/O-System scheint ja dem von Haskell ganz ähnlich zu sein: "We’ll leave the definition of IO abstract, but effectively it describes what the I/O operations to be executed are, rather than how to execute them. The resulting operations are executed externally, by the run-time system." (https://docs.idris-lang.org/en/latest/tutorial/typesfuns.html#i-o) Folgender Idris-Code gibt eine endlose Folge von "hello" aus:
1 | |
2 | |
Wenn sämtliche Auswertungen strikt erfolgen würden, würde der Code zwar in einer Endlosschleife laufen, aber nie etwas ausgeben. Da die Argumente von (>>=) nicht als Lazy deklariert sind, vermute ich, dass die Laziness im Datentyp IO steckt. Man kann ja auch in Idris endlose Datenstrukturen definieren (und IO ist ganz sicher so eine), bei denen – um dies überhaupt zu ermöglichen – bestimmte Konstruktorargumente lazy ausgwertet werden.
Gast
#7142462
Yalu X. schrieb: > Eins N00B schrieb: > Ich habe mich bisher noch nicht mit Idris beschäftigt, aber das > I/O-System scheint ja dem von Haskell ganz ähnlich zu sein: > > "We’ll leave the definition of IO abstract, but effectively it describes > what the I/O operations to be executed are, rather than how to execute > them. The resulting operations are executed externally, by the run-time > system." > (https://docs.idris-lang.org/en/latest/tutorial/typesfuns.html#i-o) Stimmt. Der eigene Code löst die Monade ja auch nicht auf; es gibt ja kein runIO. > Folgender Idris-Code gibt eine endlose Folge von "hello" aus: > main : IO () > main = putStrLn "hello" >>= \_ => main > > Wenn sämtliche Auswertungen strikt erfolgen würden, würde der Code zwar > in einer Endlosschleife laufen, aber nie etwas ausgeben. Da die > Argumente von (>>=) nicht als Lazy deklariert sind, vermute ich, dass > die Laziness im Datentyp IO steckt. Man kann ja auch in Idris endlose > Datenstrukturen definieren (und IO ist ganz sicher so eine), bei denen – > um dies überhaupt zu ermöglichen – bestimmte Konstruktorargumente lazy > ausgwertet werden. Ich stehe gerade wohl echt auf dem Schlauch. Wieso sollte der Code eager evaluated nichts ausgeben? Weil die Ausgabe auf dem Rückweg aus der Rekursion erfolgen würde? Das wäre aber die umgekehrte Reihenfolge entgegen dem, was der Code beschreibt. Vielleicht wolltest du putStrLn und main anders herum aufrufen?
Eins N00B schrieb: > Wieso sollte der Code eager evaluated nichts ausgeben? putStrLn darf selber nichts ausgeben, denn sonst wäre die Funktion nicht nebeneffektfrei. Stattdessen liefert sie als Rückgabewert eine so genannte IO-Action, die das Laufzeitsytem anweist, den String "hello" auszugeben. Dieser Vorgang wird rekursiv wiederholt. Falls die Rekursionstiefe endlich wäre, würden am Ende alle erzeugten IO-Actions mit dem Bind-Operator (>>=) zu einer großen Gesamt-IO-Action zusammengefasst und als Funktionswert des ersten main-Aufrufs an das Laufzeitsystem zurückgegeben werden. Dann würde dieses beginnen, die einzelnen IO-Actions in der Gesamt-IO-Action nacheinander abzuarbeiten, d.h nacheinander die "hello"s auszugeben. Den letzten Abschnitt ist im Konjunktiv geschrieben, weil wegen der unendlichen Rekursionstiefe die dort beschriebenen Dinge erst nach unendlich langer Zeit geschehen werden, also nie. Erfolgt die Auswertung hingegen lazy, liefert main schon nach dem ersten Aufruf von putStrLn die Gesamt-IO-Action, allerdings als unvollständige Datenstruktur. Das Laufzeitsystem kann immerhin schon einmal die erste Einzel-IO-Action daraus extrahieren und damit das erste "hello" ausgeben. Dann stellt es aber fest, dass der Rest der Gesamt-IO-Action fehlt und stößt deswegen eine weitere Auswertung an. Faul, wie das System ist, wird dann gerade so viel von dem rekursiven Ausdruck ausgewertet, dass die Gesamt-IO-Action um eine Einzel-IO-Action erweitert werden kann und das Laufzeitsystem Futter für die nächste Ausgabe von "hello" hat. Dieser Vorgang setzt sich endlos fort.
Gast
#7142544
Ron T. schrieb: > Na dann scheint das Thema ja für die übrige Welt weniger von Belang. > Vielen Dank für die konstruktiven Antworten. Vielleicht verrätst du, warum du nach den Monaden gefragt hast. Anscheinend sind sie dir irgendwie begegnet. Oder arbeitest du die Wikipedia konsequent durch und fragst nach allem, was zum Thema Programmieren gehört? ;-)
Gast
#7142607
Yalu X. schrieb: > Eins N00B schrieb: >> Wieso sollte der Code eager evaluated nichts ausgeben? > > putStrLn darf selber nichts ausgeben, denn sonst wäre die Funktion nicht > nebeneffektfrei. Stattdessen liefert sie als Rückgabewert eine so > genannte IO-Action, die das Laufzeitsytem anweist, den String "hello" > auszugeben. Dieser Vorgang wird rekursiv wiederholt. > > Falls die Rekursionstiefe endlich wäre, würden am Ende alle erzeugten > IO-Actions mit dem Bind-Operator (>>=) zu einer großen Gesamt-IO-Action > zusammengefasst und als Funktionswert des ersten main-Aufrufs an das > Laufzeitsystem zurückgegeben werden. Dann würde dieses beginnen, die > einzelnen IO-Actions in der Gesamt-IO-Action nacheinander abzuarbeiten, > d.h nacheinander die "hello"s auszugeben. > > Den letzten Abschnitt ist im Konjunktiv geschrieben, weil wegen der > unendlichen Rekursionstiefe die dort beschriebenen Dinge erst nach > unendlich langer Zeit geschehen werden, also nie. > > Erfolgt die Auswertung hingegen lazy, liefert main schon nach dem ersten > Aufruf von putStrLn die Gesamt-IO-Action, allerdings als unvollständige > Datenstruktur. Das Laufzeitsystem kann immerhin schon einmal die erste > Einzel-IO-Action daraus extrahieren und damit das erste "hello" > ausgeben. Dann stellt es aber fest, dass der Rest der Gesamt-IO-Action > fehlt und stößt deswegen eine weitere Auswertung an. Faul, wie das > System ist, wird dann gerade so viel von dem rekursiven Ausdruck > ausgewertet, dass die Gesamt-IO-Action um eine Einzel-IO-Action > erweitert werden kann und das Laufzeitsystem Futter für die nächste > Ausgabe von "hello" hat. Dieser Vorgang setzt sich endlos fort. Achso, du meinst, dass sämtliche IO actions gesammelt und dann erst ausgewertet werden? Wie kommst du darauf? Oder wo kann man was dazu lesen? Ich sehe nicht, was dagegen spricht, dass der Code, der main aufruft, den Rückgabewert der Funktion auch direkt auswertet und putStrLn sehr wohl Nebeneffekte hat. Sonst würden so Dinge wie performUnsafeIO auch nicht funktionieren, oder? Habe da leider auf die Schnelle nichts aufschlussreiches gefunden.
Eins N00B schrieb: > Ich sehe nicht, was dagegen spricht, dass der Code, der main aufruft, > den Rückgabewert der Funktion auch direkt auswertet und putStrLn sehr > wohl Nebeneffekte hat. Mit den Nebeneffekten wäre es keine reine FP mehr. Eins N00B schrieb: > Sonst würden so Dinge wie performUnsafeIO auch nicht funktionieren, > oder? Ja, performUnsafeIO hat tatsächlich Nebeneffekte. Funktionen mit "unsafe" im Namen sind Schmuddelfunktionen, die bewusst gegen die Regeln der sauberen FP verstoßen und die man deswegen nur in Ausnahmefällen (bspw. zu Debug-Zwecken) benutzen sollte.
Ron T. schrieb: > für einen unstudierten praktischen Programmierer wie mich Ich habe auch im Studium nichts mit funktionaler Programmierung gemacht. Ich weiß aber, dass funktionale Programmiersprachen Seiteneffekte so gut es geht vermeiden. Nur was tut man wenn man sie doch braucht? Analog zum unixoiden Kommandozeilen-"tee" wäre es z.B. sonst nicht möglich, eine Datenquelle mit mehreren Senken zu verbinden. Der verlinkte Artikel hat bei mir lang verloren geglaubtes Halbwissen über Haskell reaktiviert und mir drei Beispiele für Haskell gezeigt, wo und wie man Monaden braucht und sich sogar selber basteln kann. Für einen praktischen Programmierer wie z.B. für mich dennoch auch irgendwie akademisch. mfg mf
Gast
#7143151
Achim M. schrieb: > Ron T. schrieb: >> für einen unstudierten praktischen Programmierer wie mich ....dauert es Monade, bis er sich mit Monaden vertraut gemacht hat.
Brünnlein vor dem Tore schrieb: > ....dauert es Monade, bis er sich mit Monaden vertraut Eher nicht wenn sich dank gezielter Nachfragemöglichkeit in einem solchen Forum herausstellt daß das für die eigene Arbeit nie von Belang sein wird ;-)
Gast
#7143660
Achim M. schrieb: > Für einen praktischen Programmierer wie z.B. für mich dennoch auch > irgendwie akademisch. Naja, nicht ganz. Rein funktionale Sprachen sind zwar tatsächlich eher eine akademische Spielerei, aber Teile von dieser Spielerei sind tatsächlich auch praktisch durchaus relevant und immer, wo das der Fall ist, freut man sich, wenn die Akademiker die Sache zuvor mal "ausgeklingelt" haben. Mir fällt da z.B. SQL ein, insbesondere die Functions, egal ob "eingebaut" oder selbst programmiert. Hier z.B. trifft man auf diesen Scheiß und er hat hier u.U. durchaus enorme Relevanz. Also zusammenfassend: viel von diesem akademischen Kram scheint in erster Näherung völlig verzichtbare Spielerei zu sein. Tatsache ist aber: ist es nicht, es taucht in der einen oder anderen Form auch immer in der relevanten Praxis wieder auf. Nur halt nur selten in der "reinen" Form der akademischen Betrachtungen.
Gast
#7143727
Yalu X. schrieb: > Eins N00B schrieb: >> Ich sehe nicht, was dagegen spricht, dass der Code, der main aufruft, >> den Rückgabewert der Funktion auch direkt auswertet und putStrLn sehr >> wohl Nebeneffekte hat. > > Mit den Nebeneffekten wäre es keine reine FP mehr. Rein im Sinne von Nebeneffektsfrei? Sonst LISPs eignen bieten häufig ja auch FP-Funktionalität, sind aber nicht nebeneffektsfrei (zumindest nicht auf der Sprachebene. Und Haskell-Programmierer reden interessanterweise nicht wirklich davon, dass IO-Actions nebeneffektsfrei sind. Der Haskell 2010-Report (https://www.haskell.org/onlinereport/haskell2010/haskellch41.html#x49-32100041.1) ist da für mich auch nicht so eindeutig, wo die impurity eigentlich steckt. > Eins N00B schrieb: >> Sonst würden so Dinge wie performUnsafeIO auch nicht funktionieren, >> oder? > > Ja, performUnsafeIO hat tatsächlich Nebeneffekte. Funktionen mit > "unsafe" im Namen sind Schmuddelfunktionen, die bewusst gegen die Regeln > der sauberen FP verstoßen und die man deswegen nur in Ausnahmefällen > (bspw. zu Debug-Zwecken) benutzen sollte. Klar. Aber mir geht es darum, dass man so Haskell-Funktionen schreiben kann, die definitiv impure sind. Warum sollte putStrLn es nicht auch zB sein?
Gast
#7143738
c-hater schrieb: > Achim M. schrieb: > >> Für einen praktischen Programmierer wie z.B. für mich dennoch auch >> irgendwie akademisch. > > Naja, nicht ganz. Rein funktionale Sprachen sind zwar tatsächlich eher > eine akademische Spielerei, aber Teile von dieser Spielerei sind > tatsächlich auch praktisch durchaus relevant und immer, wo das der Fall > ist, freut man sich, wenn die Akademiker die Sache zuvor mal > "ausgeklingelt" haben. Es werden ja auch FP-Prinzipien in viele moderne Programmiersprachen mit aufgenommen. C++, Java, Rust, Python haben alle zum Beispiel map in ihrer Standardbibliothek und unterstützen lambda-Funktionen. Aber Monaden habe ich abseits von Spielereien noch nicht in den Mainstream-Sprachen gesehen. Außer in C# vielleicht (Scala, F# oder Clojure zähle ich nicht als Mainstream). > Mir fällt da z.B. SQL ein, insbesondere die Functions, egal ob > "eingebaut" oder selbst programmiert. Hier z.B. trifft man auf diesen > Scheiß und er hat hier u.U. durchaus enorme Relevanz. Inwiefern ist SQL funktional? > Also zusammenfassend: viel von diesem akademischen Kram scheint in > erster Näherung völlig verzichtbare Spielerei zu sein. Tatsache ist > aber: ist es nicht, es taucht in der einen oder anderen Form auch immer > in der relevanten Praxis wieder auf. Nur halt nur selten in der "reinen" > Form der akademischen Betrachtungen. FP ist eine von vielen Herangehensweisen (im Moment eher im Nischendasein), Programmierprobleme zu lösen. Und somit auch nicht verzichtbarer als Java oder C. Nur halt nicht so verbreitet. Scheint auch das ein oder andere Unternehmen zu geben mit kommerziellen Haskell-Codebases; das fällt imho nicht mehr unter „verzichtbare Spielerei“.
Gast
#7143934
Eins N00B schrieb: > Oder wo kann man was dazu > lesen? Das ist so ein bisschen schwierig, weil ein gewisse Computerprogrammiergeschichte mit drinhängt. So ganz grob kann man sagen: statt Werte werden Funktionen bearbeitet bzw. herausgegeben. Und das ist auch der Hintergrund der Monadenkonstruktion. Bevor man die Bücherei aufsucht, kann man aber schon noch auf den Wikiseiten nachsehen was es da so gibt: https://de.wikipedia.org/wiki/Haskell_(Programmiersprache) https://de.wikipedia.org/wiki/Lambda-Kalkül Der Lambda-Kalkül wird oft benutzt, um Eigenschaften von Programmiersprachen zu untersuchen bzw. diesbezüglich zu "kommunizieren" (Weil es auch ein standardisiertes Verfahren ist). Nicht zuletzt kann man auf der Haskellseite selbst nachschauen, was es da an Argumenten gibt: https://www.haskell.org Gerade der untere Part bei der "Lazy" Überschrift wird verständlicher, wenn man die Übungen mit dem Lambda-Kalkül (die hier nicht abgenommen werden können) im Hinterkopf hat. Ausprobieren sollte man den Lambda-Kalkül (falls völlig unbekannt) mit einfachen Beispielen wie 1+2, 3*4, 4^4, (2+4+8+16)^3, Binomische Formeln, Pascalsches Dreieck, der kleine Gauß usw. oder was sonst gerade viel Spaß macht, und nicht zu schwer ist. Außerdem sollte man sich ein Tutorial suchen, dass ziemlich einfach ist, denn die Grundlagen vom Lambda-Kalkül sind nicht schwierig - aber man muss sie halt auswendig wissen, und anwenden können.
Eins N00B schrieb: >> Mit den Nebeneffekten wäre es keine reine FP mehr. > > Rein im Sinne von Nebeneffektsfrei? Ja, genau. > Sonst LISPs eignen bieten häufig ja auch FP-Funktionalität, sind aber > nicht nebeneffektsfrei (zumindest nicht auf der Sprachebene. Richtig. Lisp, OCaml, F# usw. sind zwar Funktionalsprachen, aber nicht rein funktional. > Und Haskell-Programmierer reden interessanterweise nicht wirklich davon, > dass IO-Actions nebeneffektsfrei sind. Funktionen sind in Haskell nebeneffektsfrei, IO-Actions sind es nicht. Der Trick bei Haskell ist der, dass Funktionen wie bspw. putStrLn IO-Actions nur erzeugen (dazu braucht es keine Nebeneffekte), aber nicht ausführen. Deren Ausführung geschieht – wie ich oben schon geschrieben habe – außerhalb des eigentlichen Haskell-Programms. >> Ja, performUnsafeIO hat tatsächlich Nebeneffekte. Funktionen mit >> "unsafe" im Namen sind Schmuddelfunktionen, die bewusst gegen die Regeln >> der sauberen FP verstoßen und die man deswegen nur in Ausnahmefällen >> (bspw. zu Debug-Zwecken) benutzen sollte. > > Klar. Aber mir geht es darum, dass man so Haskell-Funktionen schreiben > kann, die definitiv impure sind. Warum sollte putStrLn es nicht auch zB > sein? putStrLn könnte impure sein, ist es aber nicht. Wäre es tatsächlich impure, hätten es die Haskell-Entwickler unsafePutStrLn genannt, um dies deutlich zu machen. Folgendes Beispiel zeigt, wie man in Funktionen, die kein IO zurückgeben, Debug-Meldungen realisieren kann und warum das nur unter Verwendung von unsafe-Funktionen geht:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
Ausgabe:
1 | |
2 | |
3 | |
4 | |
inc liefert einfach das um 1 erhöhte Argument zurück und tut sonst nichts. inc1 ist ein Versuch, mit putStrLn zusätzlich eine Meldung auszugeben. Obwohl mit `seq` die Auswertung von putStrLn erzwungen wird, wird die Meldung nicht ausgegeben. Das liegt daran, dass putStrLn zwar eine IO-Action erzeugt und als Funktionswert zurückgibt, dieser Funktionswert aber nirgends verwendet wird, sondern einfach verpufft. Dass die Ausgabe unterbleibt, zeigt, dass putStrLn diese nicht als Nebeneffekt eingebaut hat. In inc2 wird die von putStrLn erzeugte IO-Action an unsafePerformIO übergeben und von dieser direkt ausgeführt. Damit wird die Meldung als Nebeneffekt von unsafePerformIO tatsächlich ausgegeben. Damit ist nicht nur unsafePerformIO, sondern auch inc2 impure. In Haskell ist eine Funktion genau dann impure, wenn sie direkt oder indirekt eine Funktion aufruft, deren Name mit "unsafe" beginnt. Gäbe es die unsafe-Funktionen nicht, wäre garantiert jedes Haskell-Programm¹ nebeneffektfrei, aber dennoch I/O-fähig. ──────────── ¹) Damit meine ich nicht das komplette, vom Compiler/Linker erzeugte Binary, sondern den Teil des Programms, der durch den in Haskell geschriebenen Quellcode definiert ist. Der andere Teil, nämlich das in C geschrieben Laufzeitsystem hat natürlich sehr wohl Nebeneffekte.
Gast
#7144081
Yalu X. schrieb: > [...] Ich glaube, dass ich inzwischen Erleuchtung erlangt habe. :) Mein Verständnisproblem war, dass ich in meinem Kopf „enthält Code mit Nebeneffekten“ und „Nebeneffektsfreiheit“ vermengt habe. Ich dachte, dass deine Erklärungen auf eine Struktur ähnlich des Interpreter-Patterns bei freien Monaden hinauslaufen, wofür ich keine nachvollziehbaren Gründe gesehen habe – war ja auch nicht das, worauf du hinauswolltest. Mein aktueller Stand ist, dass putStrLn sehr wohl Code mit Nebeneffekten enthält, aber diesen nicht selbst aufruft. IO Actions wären dann nichts anderes als gewrappte Lambdas mit einer (als Implementationsdetail) bestimmten Signatur, die von dem main aufrufenden Code ausgeführt werden. Dies findet sich auch in der Definition von IO (wie ich dann auch realisiert habe):
1 | |
… und plötzlich erscheinen mir deine Erklärungen so viel nachvollziehbarer. Danke, dass du dir die Zeit genommen hast. :)
Gast
#7144090
rbx schrieb: > Eins N00B schrieb: >> Oder wo kann man was dazu >> lesen? > > […] Deine Antwort bezieht sich auf FP im allgemeinen, oder? Mit den Grundkonzepten der FP bin ich schon vertraut; auch wenn es auf theoretisch etwas wackeligen Beinen steht (habe halt keine (rein) mathematische oder informatische Ausbildung genossen). Mein Verständnisproblem war speziell bezüglich des Umgangs mit Nebeneffekten in Haskell.
Eventuell könnte noch mal jemand auf die vielzitierten ominösen "Nebeneffekte" eingehen... Was soll das sein? Der "Effekt" einer Funktion /eines Codes ist ja zunächst mal festumrissen!
Beitrag #7144250 wurde von einem Moderator gelöscht.
Gast
#7144371
Ron T. schrieb: > Eventuell könnte noch mal jemand auf die vielzitierten ominösen > "Nebeneffekte" eingehen... Nebeneffektfrei ist eine Funktion genau dann, wenn das Ergebnis einer Funktion ausschließlich von ihren Eingabeparametern abhängt, und sie immer das gleiche macht, egal wie oft und wann sie aufgerufen wird. Ein Beispiel in C:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
Die Funktion nicht_nebeneffekt_frei() ist deshalb nicht ohne Nebeneffekte weil ihr Ergebnis außer ihrer Parameter noch von der Anzahl ihrer Aufrufe abhängt. In Funktionalen Sprachen ist eine solche Funktion nicht möglich. So gibt es z.B. in der hier oft genannten Sprache Haskell deshalb keine Variablen, und keine Schleifen! Trotzdem, bzw. genau deswegen, kann man mit dieser Sprache perfekt und sehr elegant programmieren...
Gast
#7144373
Ach, ich hab im Beispielprogramm bei printf() was vergessen. Sollte aber klar sein, oder?
"Nebeneffekt" ist die deutsche Übersetzung von "side effect". Der erste Satz im englischen Wikipedia-Artikel erklärt das IMHO sehr gut: https://en.wikipedia.org/wiki/Side_effect_(computer_science) "In computer science, an operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, which is to say if it has any observable effect other than its primary effect of returning a value to the invoker of the operation."
Gast
#7144397
Ein anderer Programmierer schrieb: > Nebeneffektfrei ist eine Funktion genau dann, wenn das Ergebnis einer > Funktion ausschließlich von ihren Eingabeparametern abhängt, und sie > immer das gleiche macht, egal wie oft und wann sie aufgerufen wird. Und als Implikation keinen globalen Zustand verändert. printf() hat zB den Nebeneffekt, Dinge auf stdout auszugeben. Imho ist es auch außerhalb von FP sinnvoll, über Nebeneffekte nachzudenken. Ohne Nebeneffekte lassen sich Funktionen besser (Unit-)testen, Programme besser nachvollziehen und außerdem sind optimierungen möglich, die es sonst nicht wären. Ein anderer Programmierer schrieb: > In Funktionalen Sprachen ist eine solche Funktion nicht möglich. So gibt > es z.B. in der hier oft genannten Sprache Haskell deshalb keine > Variablen, und keine Schleifen! Zumindest ist es nicht so … üblich und es endet deutlich hässlicher als in C. Ich lehne mich mal so weit aus dem Fenster, zu sagen, dass die Abwesenheit von Variablen und Schleifen nichts mit Nebeneffekten zu tun hat, sondern eher eine zusätzlich unübliche Design-Entscheidung ist. Lokale Variablen sind ja sehr wohl mit pure functions vereinbar. Die meisten funktionalen Programmiersprachen haben im Gegensatz zu Haskell übrigens nicht nur fast ausschließlich nebeneffektsfreie Funktionen und auch Variablen. Allerdings sind sie häufig multiparadigmatisch: LISP-Dialekte, Scala, F#, ocaml afaik, …
Gast
#7144555
Bei vielen wichtigen Fragen kann man meist auch ganz gute Antworten im Real World Haskell Buch finden: (strg f "side") http://book.realworldhaskell.org/read/types-and-functions.html http://book.realworldhaskell.org/read/io.html http://book.realworldhaskell.org/read/interfacing-with-c-the-ffi.html Grob gesagt kann man wieder von mathematischen Formeln ausgehen, die sich normalerweise auch nicht verändern, außer durch Verbesserung oder ähnlichem. So denke ich bei Nebeneffekt eher an Probleme mit Fließkommazahlen - die aber bei Haskell trotz der ständigen Hinweise auf "Sicherheit" auch nicht besser rüberkommen. Dazu braucht es spezielle Bibliotheken, die dem abhelfen. Man sollte aber nicht denken, dass man sich bei der Haskell-Community groß Gedanken darüber macht. Der Nebeneffekt ist dann, dass man lieber zu anderen Programmiersprachen wechselt, die bessere Bibliotheken diesbezüglich mitbringen. Also von wegen "Nebeneffektfrei" ;)
Ein anderer Programmierer schrieb: > Die Funktion nicht_nebeneffekt_frei() ist deshalb nicht ohne > Nebeneffekte weil ihr Ergebnis außer ihrer Parameter noch von der Anzahl > ihrer Aufrufe abhängt. Das scheint mir aber auf eine bloße Definitionsfrage des Begriffs Eingangsparameter hinauszulaufen: Strenggenommen sind doch alle verwendeten Variablen egal welcher Quelle letztlich Eingangsparameter, selbst wenn die Funktion nicht explizit mit ihnen aufgerufen wird.
Ron T. schrieb: > Ein anderer Programmierer schrieb: >> Die Funktion nicht_nebeneffekt_frei() ist deshalb nicht ohne >> Nebeneffekte weil ihr Ergebnis außer ihrer Parameter noch von der Anzahl >> ihrer Aufrufe abhängt. > > Das scheint mir aber auf eine bloße Definitionsfrage des Begriffs > Eingangsparameter hinauszulaufen: Strenggenommen sind doch alle > verwendeten Variablen egal welcher Quelle letztlich Eingangsparameter, > selbst wenn die Funktion nicht explizit mit ihnen aufgerufen wird. Schon, aber das ist doch der Witz an Funktionen, dass sie explizite (definierte) Eingangswerte erwarten und nicht einfach so auf alles global rumliegende zugreifen und (wenns blöd läuft) das dann auch noch verändern können...
Georg A. schrieb: > nicht einfach so auf alles > global rumliegende zugreifen und (wenns blöd läuft) das dann auch noch > verändern können... Das hört sich für mich eher danach an daß der Programmierer sein System nicht im Griff hat ;-)
Gast
#7144744
Ron T. schrieb: > Ein anderer Programmierer schrieb: >> Die Funktion nicht_nebeneffekt_frei() ist deshalb nicht ohne >> Nebeneffekte weil ihr Ergebnis außer ihrer Parameter noch von der Anzahl >> ihrer Aufrufe abhängt. > > Das scheint mir aber auf eine bloße Definitionsfrage des Begriffs > Eingangsparameter hinauszulaufen: Strenggenommen sind doch alle > verwendeten Variablen egal welcher Quelle letztlich Eingangsparameter, > selbst wenn die Funktion nicht explizit mit ihnen aufgerufen wird. Selbst unter der Betrachtung finde ich die Aussicht, dass eine Funktion sicher 3 Parameter statt potentiell 50, deutlich besser. Bei einem Programm mit 100 Zeilen wäre mir das noch recht egal, aber mit ein paar Nullen mehr nicht mehr so.
Ron T. schrieb: > Das hört sich für mich eher danach an daß der Programmierer sein System > nicht im Griff hat ;-) Ein Programmierer hat sein System (und vor allem sich selbst) nie im Griff ;) Aber das sind eben die beiden Ecken bzw. Sichtweisen, von denen die Informatik herkommt: Etwas überspitzt auf der einen Seite die Physiker, die einfach nur mal eben schnell was berechnen wollen (dann kommt so Grütze wie Fortran raus) und auf der anderen Seite die Mathematiker, die erstmal zweckfrei ein wohldefiniertes und zwangsweise sauberes Universum schaffen wollen (Algol 60, wurde nie wirklich fertig, kennt keiner mehr). Gewonnen haben die Pfuscher... Und nur ein kleines unbeugsames Dorf verteidigt die Prinzipien der funktionalen Programmierung ;) BTW: Wie sagte einst unser Prof (Eickel) im ersten Semester Informaktik: "Mir ist es lieber, wenn ein Erstsemester noch kein Vorwissen hat, dann ist er noch nicht verdorben".
Georg A. schrieb: > BTW: Wie sagte einst unser Prof (Eickel) im ersten Semester Informaktik: > "Mir ist es lieber, wenn ein Erstsemester noch kein Vorwissen hat, dann > ist er noch nicht verdorben". Aber danach ist er es, oder wie ist dieser Satz zu verstehen? ;-)
Gast
#7145329
Ron T. schrieb: > Das hört sich für mich eher danach an daß der Programmierer sein System > nicht im Griff hat ;-) Die Konsequenzen bzw. Vorteile von seiteneffektfreien Funktionen gehen weit über Dein aktuelles Vorstellungsvermögen hinaus.
Gast
#7145556
Georg A. schrieb: > Aber das sind eben die beiden Ecken bzw. Sichtweisen, > von denen die Informatik herkommt: Etwas überspitzt > auf der einen Seite die Physiker, die einfach nur mal > eben schnell was berechnen wollen (dann kommt so Grütze > wie Fortran raus) und auf der anderen Seite die > Mathematiker, die erstmal zweckfrei ein wohldefiniertes > und zwangsweise sauberes Universum schaffen wollen > (Algol 60, wurde nie wirklich fertig, kennt keiner > mehr). Du vergisst (wie alle anderen auch) das winzige Grüppchen derjenigen, die den Blechtrottel zu einer sinnvollen Zusammenarbeit mit einem realen Vorgang in der realen Welt bringen wollen. Dieser Vorgang in der realen Welt kann eine Maschine sein, die gesteuert werden soll, aber auch ein Mensch, der verzweifelt Daten über ein GUI einzugeben versucht. > Gewonnen haben die Pfuscher... Und nur ein kleines > unbeugsames Dorf verteidigt die Prinzipien der > funktionalen Programmierung ;) Es wäre schon sehr viel gewonnen, wenn abstrakte Modelle nicht ständig auf Situationen angewendet würden, für die sie nicht geeignet sind. Eine streng sequenzielle Modellmaschine eignet sich nur sehr bedingt zur Beschreibung, wenn zahlreiche Dinge parallel erledigt werden sollen. Funktionale Programmierung ist adäquat bei Berechnungen , weil man es sich dort leisten kann, die ganzen "schlechten", "künstlichen" Zwischenzustände, die nur wegen des streng sequenziellen Maschinenmodelles entstehen, einfach zu ignorieren. Sie wird aber m.E. zur Farce, wenn die Zustände intrinsischer Bestandteil des Problems sind -- weil der externe Vorgang in der realen Welt nämlich tatsächlich verschiedene Zustände hat...
Ein anderer Programmierer schrieb: > Die Konsequenzen bzw. Vorteile von seiteneffektfreien Funktionen gehen > weit über Dein aktuelles Vorstellungsvermögen hinaus. Die spitze Bemerkung kann natürlich niemanden zufriedenstellen. Insbesondere nachdem der Adressat ohne tatsächlich mitgelieferte Fakten nicht überblickt wie weit der Horizont des Absenders nun wirklich reicht. Ich bin mir aber eigentlich sicher, daß sich viele Fragestellungen in meinem drögen, aber langjährigen, einfachsprachlichen Programmiereralltag schon gezeigt haben ohne je so benannt zu werden bzw. benannt werden zu müssen. Und so manches scheint mir da künstlich undoder eher dem Compiler-Bauer als dem Anwender entgegenzukommen (z.B. Typisierung). Georg A. schrieb: > Gewonnen haben die Pfuscher... Gewinnen tut der Praktiker, der ein Problem schnellstmöglich zu lösen imstande ist. Da dürfte sich unter der Lupe stets Pfusch zeigen- als Ausdruck der Unvollkommenheit jeder schnellen Lösung und schlussendlich des Menschen. Georg A. schrieb: > Wie sagte einst unser Prof (Eickel) im ersten Semester Informaktik: "Mir > ist es lieber, wenn ein Erstsemester noch kein Vorwissen hat, dann ist > er noch nicht verdorben". Daraus wird auch deutlich wieviel Psychologie, Individualität, ja blanke Willkür das programmsprachliche Spiel beherrscht. Die Verderbnis des einen ist die Ideologie des anderen.
Als Nutzer nicht-funktionaler Sprachen sind wir es gewohnt, unsere Programme so zu betrachten, dass sie den globalen Zustand des Programms manipulieren. Der globale Zustand des Programms setzt sich zusammen aus den Objekten, deren Lebensdauer der Lebensdauer des Programms entspricht. Ganz praktisch können dies eben nicht-lokale Objekte sein bspw. als globale Variablen oder Objekte auf dem Heap eines C / C++ Programms. Genau dies ist im funktionalen Paradigma nicht möglich. Trotzdem möchte man in funktionalen Sprachen für praktische Anwendungen so etwas wie einen (globalen) Zustand eines Programmes manipulieren, etwa weil das Programm ein simpler Zustandsautomat ist. Die Idee ist nun, den (globalen) Zustand von Funktion zu Funktion "durchzureichen" statt globale Variablen / Objekte auf dem Heap zu manipulieren. Natürlich kann man das auch in prozeduralen Sprachen so machen:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
statt
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
BTW: natürlich hat das ganze sein Ende, wenn etwa bei einem µC eine LED eingeschaltet oder eine Taste abgefragt werden soll:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
Denn readButton() und setLed() können nicht reine Funktionen sein, weil sie den (globalen) Zustand einiger HW-Register o.ä. lesen / setzen müssen. Als Monad bezeichnet man (vereinfacht, darum geht es hier) einen Datentyp, der ein Ergebnis zusammen mit einer Zustandsinformation kapselt und darauf anwendbare Operationen. In C sind das freie Funktionen, und in C++ freie oder Elementfunktionen. (Die Vorstufe eines Monads sind Funktoren: diese kapseln nur ein Ergebnis mit einer Operation. In z.B. C++ einfach eine (generische) Klasse mit Call-Operator). Damit kann man etwa std::optional<> und darauf anwendbare (freie) Funktionen als Monaden betrachten, denn std::optional<> kapselt ein Ergebnis mit einem Zustand (ob überhaupt ein Ergebniswert vorliegt). Dies macht sich ganz praktisch etwa bei aufeinanderfolgenden Operation inkl. der notwendigen Fehlerbehandlung bemerktbar. Zunächst ein rein prozedurales Beispiel in C ohne Monaden-Nachbildung. Das Ergebnis ist vom DT int, implizit wird hier wie oft üblich in C der Fehlerzustand in die negative Hälfe des int abgebildet. Dies führt zu den oft üblichen Fehlercodeprüfungen:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
Auch in C++ mit std::optional<> wird es zunächst nicht besser:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
Besser wird es, wenn man den Funktor std::optional<> zu einem Monad macht, indem man eine anwendbare Operation (hier: transform) hinzufügt. Hier ist transform() eine generische Operation, die auf std::optional<> angewendet werden kann, und ihrerseits wieder eine Operation (das Callable F) darauf anwendet (lifting):
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
Dann wird daraus:
1 | |
2 | |
3 | |
Die obige Verschachtelung ist natürlich auch syntaktisch nicht so gut, daher kann man sich entsprechende Hilfsobjekte generieren und etwa den |-Operator überladen (s.a. ranges-Library in C++). So wird es etwas übersichlicher:
1 | |
2 | |
3 | |
Monaden sind also im Kern nichts weiter als Datentypen, die Ergebnisse und Zustandsinformationen dazu kapseln, sowie Operationen darauf, die basierend auf der Zustandsinformation generisch das Anwenden von Operationen der Nutzergebnisse ermöglichen. Ich denke, in dieser simplen Art hat das schon jeder mal gemacht, ohne den Begriff Monad dafür zu verwenden. P.S.: Im Eingangspost war nach einem einfachen, praktischen Beispiel gefragt. Genau das ist das hier, nicht mehr.
Wilhelm M. schrieb: > template<typename T, typename F> > auto transform(const std::optional<T>& o, F f) { > ... Diese Funktion gibt es in C++23 schon fertig und heißt dort std::optional<T>::and_then. Sie entspricht dem bind-Operator >>= in Haskell mit dem Unterschied, dass and_then spezifisch für std::optional und >>= generisch für alle Monadentypen ist. In https://en.cppreference.com/w/cpp/utility/optional wird and_then auch tatsächlich als "monadic operation" bezeichnet. Hier ist ein komplettes Beispiel für die Verwendung von and_then:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
41 | |
42 | |
43 | |
44 | |
45 | |
46 | |
Ausgabe:
1 | |
2 | |
3 | |
4 | |
Dasselbe in Haskell mit drei verschiedenen Möglichkeiten, die drei Funktionen f1, f2 und f3 zu einer zu verketten (der Typ std::optional heißt dort Maybe):
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
Ausgabe:
1 | |
2 | |
3 | |
4 | |
> (Die Vorstufe eines Monads sind Funktoren: diese kapseln nur ein > Ergebnis mit einer Operation. In z.B. C++ einfach eine (generische) > Klasse mit Call-Operator). Mit dem Begriff Funktor muss man etwas vorsichtig sein, da er je nach Programmiersprache (u.a. Haskell, SML, Prolog und C++) eine völlig unterschiedliche Bedeutung hat. Hier meinst du offensichtlich ein callable Object in C++.
Yalu X. schrieb: > Diese Funktion gibt es in C++23 schon fertig und heißt dort > std::optional<T>::and_then. Diese Bemerkung habe ich mir wirklich verkniffen, denn wenn ich einen Hinweis auf neuere / zukünftige Dinge bei C++ hier im Forum mache, werde ich ja regelmäßig gesteinigt ;-) Yalu X. schrieb: >> (Die Vorstufe eines Monads sind Funktoren: diese kapseln nur ein >> Ergebnis mit einer Operation. In z.B. C++ einfach eine (generische) >> Klasse mit Call-Operator). > > Mit dem Begriff Funktor muss man etwas vorsichtig sein, da er je nach > Programmiersprache (u.a. Haskell, SML, Prolog und C++) eine völlig > unterschiedliche Bedeutung hat. Hier meinst du offensichtlich ein > callable Object in C++. Genau deswegen habe ich ja auch die Erläuterung dazu geschrieben.
Wilhelm M. schrieb: > Yalu X. schrieb: >> Diese Funktion gibt es in C++23 schon fertig und heißt dort >> std::optional<T>::and_then. > > Diese Bemerkung habe ich mir wirklich verkniffen, denn wenn ich einen > Hinweis auf neuere / zukünftige Dinge bei C++ hier im Forum mache, werde > ich ja regelmäßig gesteinigt ;-) Immerhin unterstützen die aktuellen Releases von GCC und Clang dieses Feature bereits, so dass man zum Ausprobieren nicht erst den Compiler aus Prerelease-Quellen selber bauen muss. Sonst hätte ich mich damit auch zurückgehalten.
Antwort schreiben
Bitte melde dich an, um einen Beitrag zu schreiben.