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?
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?
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.
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:
a <- [1 .. limit] -- geht die Zahlen von 1 bis limit durch
6
guard $ odd a -- gerade a herausfiltern
7
b <- [a .. limit] -- geht die Zahlen von a bis limit durch
8
guard $ odd b -- gerade b herausfiltern
9
-- a und b sind nun alle verbleibenden Permutationen
10
pure $ a * b -- Produkte bilden
11
12
main = print $ oddProducts 10
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).
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.
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
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.
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.
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
main : IO ()
2
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.
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.
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? ;-)
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
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 ;-)
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.
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?
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“.
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
import System.IO.Unsafe
2
3
inc x = x + 1
4
inc1 x = (putStrLn $ "inc1 " ++ show x) `seq` (x + 1)
5
inc2 x = (unsafePerformIO $ putStrLn $ "inc2 " ++ show x) `seq` (x + 1)
6
7
main = do
8
print $ inc 0
9
print $ inc1 10
10
print $ inc2 20
Ausgabe:
1
1
2
11
3
inc2 20
4
21
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.
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
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
… und plötzlich erscheinen mir deine Erklärungen so viel
nachvollziehbarer.
Danke, dass du dir die Zeit genommen hast. :)
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!
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
intnicht_nebeneffekt_frei(inta,intb)
2
{
3
staticintnebeneffekt=0;
4
5
nebeneffekt++;
6
7
returna+b+nebeneffekt;
8
}
9
10
intmain()
11
{
12
intsumme;
13
for(inti=0;i<10;i++)
14
{
15
summe=nicht_nebeneffekt_frei(i,10);
16
}
17
printf("main summe: %d\n");
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...
"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."
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, …
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.htmlhttp://book.realworldhaskell.org/read/io.htmlhttp://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 ;-)
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? ;-)
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.
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
intmain(){
2
States;
3
while(true){
4
s=ff1(ff2(ff3(s)));
5
}
6
}
statt
1
States;// globaler Zustand
2
intmain(){
3
while(true){
4
fp1();// manipuliert s
5
fp2();// manipuliert s
6
fp3();// manipuliert s
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
intmain(){
2
States;
3
while(true){
4
s=readButton();// Seiteneffekt
5
s=ff1(ff2(ff3(s)));
6
setLed(s);// Seiteneffekt
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
intg(){
2
intr1=fp1();
3
if(r1<0){
4
return-1;
5
}
6
assert(r1>=0);// r1 contains usable result
7
intr2=fp2(r1);
8
if(r2<0){
9
return-2;
10
}
11
assert(r2>=0);
12
intr3=fp3(r2);
13
if(r3<0){
14
return-3;
15
}
16
returnr3;
17
}
Auch in C++ mit std::optional<> wird es zunächst nicht besser:
1
std::optional<int>g(autod){
2
autor1=fp1(d);
3
if(!r1){
4
return{};
5
}
6
assert(r1);// r1 contains usable result
7
autor2=fp2(*r1);
8
if(!r2){
9
return{};
10
}
11
assert(r2);
12
autor3=fp3(*r3);
13
if(!r3){
14
return{};
15
}
16
returnr3;
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):
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
std::optional<int>g(autod){
2
returnd|bind(fp1)|bind(fp2)|bind(fp3);
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
#include<iostream>
2
#include<cmath>
3
#include<optional>
4
5
// Drei Funktionen, die nicht für alle double-Werte definiert sind und
6
// ggf. kein Ergebnis liefern
7
8
std::optional<double>f1(doublex){
9
if(x!=0.0)
10
return1.0/x;
11
returnstd::nullopt;
12
}
13
14
std::optional<double>f2(doublex){
15
if(x>=0.0)
16
returnsqrt(x);
17
returnstd::nullopt;
18
}
19
20
std::optional<double>f3(doublex){
21
if(fabs(x)<=1.0)
22
returnasin(x);
23
returnstd::nullopt;
24
}
25
26
// Nacheinanderaufruf aller drei Funktionen
27
28
std::optional<double>f123(doublex){
29
returnf1(x).and_then(f2).and_then(f3);
30
}
31
32
intmain(){
33
// Aufruf der Verkettung für verschiedene Argumente
34
// 2.0: läuft fehlerfrei durch
35
// 0.0: führt zu Fehler in f1
36
// -2.0: führt zu Fehler in f2
37
// 0.1: führt zu Fehler in f3
38
39
for(doublex:{2.0,0.0,-2.0,0.1}){
40
autoy=f123(x);
41
if(y)
42
std::cout<<*y<<'\n';
43
else
44
std::cout<<"Error\n";
45
}
46
}
Ausgabe:
1
0.785398
2
Error
3
Error
4
Error
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
import Control.Monad
2
3
-- Drei Funktionen, die nicht für alle double-Werte definiert sind und
4
-- ggf. kein Ergebnis liefern
5
6
f1 x | x /= 0 = Just (1 / x)
7
| otherwise = Nothing
8
9
f2 x | x >= 0 = Just (sqrt x)
10
| otherwise = Nothing
11
12
f3 x | abs x <= 1 = Just (asin x)
13
| otherwise = Nothing
14
15
-- Drei Alternativen, die drei Funktionen zu einer zu verketten
16
17
-- 1. Klassisch (>>= ist der bind-Operator für Monaden):
18
19
f123 x = f1 x >>= f2 >>= f3
20
21
-- 2. Äquivalent zu (1), aber eleganter, da pointfree (>=> ist der
22
-- Verkettungsoperator für Kleisli-Pfeile):
23
24
f123a = f1 >=> f2 >=> f3
25
26
-- 3. Imperative Schreibweise (entspricht (1) mit syntaktischem Zucker
27
-- und zusätzlichen Variablen für Zwischenergebnisse):
28
29
f123b x = do
30
y1 <- f1 x
31
y2 <- f2 y1
32
y3 <- f3 y2
33
return y3
34
35
main = do
36
forM_ [ 2.0, 0.0, -2.0, 0.1 ] (print . f123)
Ausgabe:
1
Just 0.7853981633974484
2
Nothing
3
Nothing
4
Nothing
> (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.