Forum: Compiler & IDEs An/Verwendung für Monaden


von Ron T. (rontem)


Lesenswert?

Kann mir hier jemand Sinn und Verwendung von
https://de.m.wikipedia.org/wiki/Monade_(Informatik) an einem 
verständlichen praktischen Beispiel erklären?

von Achim M. (minifloat)


Lesenswert?


von Ron T. (rontem)


Lesenswert?

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?

: Bearbeitet durch User
von Mona Lisa (Gast)


Lesenswert?

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.

von Ron T. (rontem)


Lesenswert?

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?

von Mona Lisa (Gast)


Lesenswert?

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.

von Eins N00B (Gast)


Lesenswert?

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
int main() {
2
    printf("Hello\n");
3
    printf("World\n");
4
    return 0;
5
}

Haskell:
1
main = putStrLn "Hello" >> putStrLn "World"

von Eins N00B (Gast)


Lesenswert?

Oder noch ein anderes Beispiel:
1
import Control.Monad (guard)
2
3
oddProducts :: Integer -> [Integer]
4
oddProducts limit = do
5
  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).

von Ein anderer Programmierer (Gast)


Lesenswert?

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.

von rbx (Gast)


Lesenswert?

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.

von Alexander S. (alesi)


Lesenswert?

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

von Eins N00B (Gast)


Lesenswert?

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.

von Ein anderer Programmierer (Gast)


Lesenswert?

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.

von Ron T. (rontem)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Eins N00B (Gast)


Lesenswert?

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?

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Eins N00B (Gast)


Lesenswert?

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?

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Wolfgang (Gast)


Lesenswert?

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? ;-)

von Eins N00B (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Achim M. (minifloat)


Lesenswert?

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

von Brünnlein vor dem Tore (Gast)


Lesenswert?

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.

von Ron T. (rontem)


Lesenswert?

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 ;-)

von c-hater (Gast)


Lesenswert?

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.

von Eins N00B (Gast)


Lesenswert?

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?

von Eins N00B (Gast)


Lesenswert?

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“.

von rbx (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Eins N00B (Gast)


Lesenswert?

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. :)

von Eins N00B (Gast)


Lesenswert?

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.

von Ron T. (rontem)


Lesenswert?

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!

: Bearbeitet durch User
Beitrag #7144250 wurde vom Autor gelöscht.
von Ein anderer Programmierer (Gast)


Lesenswert?

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
int nicht_nebeneffekt_frei(int a, int b)
2
{
3
  static int nebeneffekt = 0;
4
5
  nebeneffekt++;
6
7
  return a + b + nebeneffekt;
8
}
9
10
int main()
11
{
12
  int summe;
13
  for (int i=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...

von Ein anderer Programmierer (Gast)


Lesenswert?

Ach, ich hab im Beispielprogramm bei printf() was vergessen. Sollte aber 
klar sein, oder?

von Yalu X. (yalu) (Moderator)


Lesenswert?

"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."

von Eins N00B (Gast)


Lesenswert?

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, …

von rbx (Gast)


Lesenswert?

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" ;)

von Ron T. (rontem)


Lesenswert?

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.

von Georg A. (georga)


Lesenswert?

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...

von Ron T. (rontem)


Lesenswert?

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 ;-)

von Eins N00B (Gast)


Lesenswert?

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.

von Georg A. (georga)


Lesenswert?

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".

von Yalu X. (yalu) (Moderator)


Lesenswert?

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? ;-)

von Ein anderer Programmierer (Gast)


Lesenswert?

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.

von Grummler (Gast)


Lesenswert?

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...

von Ron T. (rontem)


Lesenswert?

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.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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
int main() {
2
    State s;
3
    while(true) {
4
        s = ff1(ff2(ff3(s)));
5
    }
6
}

statt
1
State s; // globaler Zustand
2
int main() {
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
int main() {
2
    State s;
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
int g() {
2
    int r1 = fp1();
3
    if (r1 < 0) {
4
       return -1;
5
    }
6
    assert(r1 >= 0); // r1 contains usable result
7
    int r2 = fp2(r1);
8
    if (r2 < 0) {
9
       return -2;
10
    }
11
    assert(r2 >= 0);
12
    int r3 = fp3(r2);
13
    if (r3 < 0) {
14
       return -3;
15
    }
16
    return r3;
17
}

Auch in C++ mit std::optional<> wird es zunächst nicht besser:
1
std::optional<int> g(auto d) {
2
    auto r1 = fp1(d);
3
    if (!r1) {
4
       return {};
5
    }
6
    assert(r1); // r1 contains usable result
7
    auto r2 = fp2(*r1);
8
    if (!r2) {
9
       return {};
10
    }
11
    assert(r2);
12
    auto r3 = fp3(*r3);
13
    if (!r3) {
14
       return {};
15
    }
16
    return r3;
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
template<typename T, typename F>
2
auto transform(const std::optional<T>& o, F f) {
3
    if (o) {
4
        return f(*o);
5
    }
6
    return std::optional<T>{};
7
}

Dann wird daraus:
1
std::optional<int> g(auto d) {
2
   return transform(transform(transform(d, fp1), fp2), fp3);
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
std::optional<int> g(auto d) {
2
   return d | 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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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(double x) {
9
  if (x != 0.0)
10
    return 1.0 / x;
11
  return std::nullopt;
12
}
13
14
std::optional<double> f2(double x) {
15
  if (x >= 0.0)
16
    return sqrt(x);
17
  return std::nullopt;
18
}
19
20
std::optional<double> f3(double x) {
21
  if (fabs(x) <= 1.0)
22
    return asin(x);
23
  return std::nullopt;
24
}
25
26
// Nacheinanderaufruf aller drei Funktionen
27
28
std::optional<double> f123(double x) {
29
  return f1(x).and_then(f2).and_then(f3);
30
}
31
32
int main() {
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 (double x : { 2.0, 0.0, -2.0, 0.1 }) {
40
    auto y = 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++.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.