Option Type als Rückgabewert.

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
burli
User
Beiträge: 1156
Registriert: Dienstag 9. März 2004, 18:22

__deets__ hat geschrieben: Freitag 3. September 2021, 10:55 Ich finde Python hat schon immer funktionale Programmierung unterstuetzt. Denn Summentypen sind bei Python ja nun schlicht eingebaut dadurch, dass ich einfach jeden Typ benutzen kann. Ich kann None, 10, oder "tausend" zurueckgeben. Ein Summentyp aus None, int, str. Und ueber den Typen kann ich dann auch verzweigen.
Wenn man None oder 10 zurück gibt, gibt man jedes Mal einen anderen Typ zurück. Die Idee in der funktionalen Programmierung ist aber, einen eindeutigen Typ zurück zu geben.

Wenn man ein Tuple zurück gibt mit einem Feld für den Wert und einem Feld für zB None hat man zwar einen eindeutigen Typ, aber der hat keinen eindeutigen Wert. In dem Tuple kann dann ein Wert, oder None, oder beides, oder nichts stehen. Es sollte aber eindeutig sein, also entweder ein Wert "oder" None.

Python unterstützt da also gar nichts was der Idee der funktionalen Programmierung entspricht.
Das schwierigste beim Programmieren ist, sinnvolle Variablen- und Funktionsnamen zu finden :lol:
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wer sagt denn, dass das so "sollte"? Natuerlich kannst du dir eine beliebige Eigenschaft von Julia (oder jeder statisch typisierten Sprache, C++ geht genauso) hernehmen und sagen "DAS ist der Kern des ganzen, ohne das ist es nix". Na dann. Argument gewonnen.

Aber nur weil diese Sprachen nicht damit klarkommen, dass man verschiedene Typen zurueckgibt, muessen sie ubere die Kruecke "Summentyp" gehen. Genauso koennten sie auch wahlweise mit unterschiedlichen Typen als Rueckgabewerte arbeiten, die bei generischem Code gar kein pattern matching braeuchten. Und erst, wenn es nicht mehr so geht, oder du es nicht willst, dir basierend auf dem Typ ein pattern-Matching bauen.

Ich sage nicht, dass das unbedingt besser ist. Aber es *geht*, und es macht bestimmte Dinge einfacher, und andere schwerer. Darauf also so isoliert rumzureiten... tjoa.

Die Definition, die ich da zugrunde lege, besteht in der Vermeidung von Seiteneffekten, Rekursion, Funkionen hoeherer Ordnung, und generischen Funktionen, die in statischen Sprachen per Typvariablen und in Python per Ducktyping arbeiten. Das reicht mir persoenlich, um an bestimmten Stellen eben algorithmisch mit den Vorteilen eines funktionalen Ansatzes zu arbeiten. Darfst du natuerlich anders sehen. Und Julia benutzen.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Funktionale Programmierung und statische Typen sind orthogonale Konzepte. Lisp kommt ganz gut ohne statische Typisierung aus.
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@burli: Ich hatte es ja schon mal gefragt und keine Antwort erhalten: Was ist an `None` und ggf. Ausnahmen auszusetzen?

Ich mag funktionale Programmierung, und das ist auch etwas was mir an Python schon von Anfang an gefallen hat, dass man da *auch* funktional mit programmieren kann, ohne gezwungen zu werden das *ausschliesslich* zu tun.

Wenn man mal 10 und mal `None` zurück gibt, dann gibt man auch *einen* Typ zurück. Und zwar ``Optional[int]`` (ist äquivalent zu ``Union[int, None]``).

Das mit den festen Typen gehört zu funktionaler Programmierung genau so dazu wie Klassen zu objektorientierter Programmierung: Nämlich gar nicht. Das ist ein orthogonales Konzept. Es sei denn Du willst so etwas wie Scheme oder Lisp nicht zu funktionalen Programmiersprachen zählen.

@__deets__: Als ich mit Python angefangen habe gab es Perl auch schon, aber ich habe mich *wegen* Perl nach einer anderen Programmiersprache umgesehen. Die Referenzen und Sigils haben mich ziemlich genervt, aber als es dann zu OOP überging, fand ich das bei Perl extrem nachträglich hässlich drauf getackert. Bei Python mochte ich die Syntax, und das sich das alles, inklusive OOP, mehr wie Java verhalten hat, ohne die ganzen starren und sperrigen Typdeklarationen von Java.

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
burli
User
Beiträge: 1156
Registriert: Dienstag 9. März 2004, 18:22

__blackjack__ hat geschrieben: Freitag 3. September 2021, 14:51 @burli: Ich hatte es ja schon mal gefragt und keine Antwort erhalten: Was ist an `None` und ggf. Ausnahmen auszusetzen?
Sorry für die späte Antwort, aber mir ist gerade ein Video über den Weg gelaufen, das besser erklären kann als ich, warum Ausnahmen schlecht sind und warum es zB in Rust keine Ausnahmen gibt. Da musste ich an diesen Thread denken.

Eine Exception ist im Prinzip ein Goto Befehl. Und welche (ernstzunehmende) Sprache hat heute noch Goto?



https://www.youtube.com/watch?v=sbVxq7nNtgo
Das schwierigste beim Programmieren ist, sinnvolle Variablen- und Funktionsnamen zu finden :lol:
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

C? C++? Und auch wenn Rust es nicht exceptions nennt, was außer einem genauso impliziten Goto ist denn bitte der ?-Operator, der eine Funktion sofort beendet? Nach der Logik ist das also mitnichten besser.
burli
User
Beiträge: 1156
Registriert: Dienstag 9. März 2004, 18:22

__deets__ hat geschrieben: Sonntag 11. September 2022, 08:42 C? C++? Und auch wenn Rust es nicht exceptions nennt, was außer einem genauso impliziten Goto ist denn bitte der ?-Operator, der eine Funktion sofort beendet? Nach der Logik ist das also mitnichten besser.
Sorry, aber das ist völliger Käse.

https://doc.rust-lang.org/reference/exp ... k-operator

Aus einer Funktion wird nicht einfach heraus gesprungen wie bei einer Exception. Es gibt immer einen "geordneten Rückzug", soll heißen, ein Ausdruck liefert immer ein Ergebnis zurück. Entweder einen gültigen Wert oder einen Error Type, und zwar immer eingepackt in einem Result Type.

Um das Beispiel aus dem Video aufzugreifen: die num_parse() Funktion würde in Python entweder eine Zahl zurückgeben oder mit eine Exception beendet. Man springt also quasi mit einem Goto aus der Funktion an einen vorher definierten Punkt.

In Rust haben die meisten Funktionen einen Result Type als Rückgabewert, den man irgendwie außerhalb der Funktion behandeln muss. Der Fehler tritt also in einer Funktion auf, wird aber nicht innerhalb der Funktion behandelt. Man gibt nicht nur den erwartetet Rückgabewert zurück sondern alternativ einen Fehler. Diesen Fehlen muss die aufrufende Funktion selbst behandeln. Der einfachste Fall ist .unwrap(). Die Funktion packt entweder den eigentlichen Rückgabewert aus oder beendet das Programm mit einem panic. Das funktioniert und ist in so fern sicher als dass das Programm bei einem Fehlen keinen Blödsinn macht. Das sollte man aber nur in der Entwicklungsphase verwenden. Stattdessen sollte man einen Rückgabewert immer mit einem match auf dessen Inhalt überprüfen. Das Fragezeichen ist nur syntaktischer Zucker für ein match.


https://doc.rust-lang.org/book/ch09-02- ... ing-errors
The ? placed after a Result value is defined to work in almost the same way as the match expressions we defined to handle the Result values in Listing 9-6. If the value of the Result is an Ok, the value inside the Ok will get returned from this expression, and the program will continue. If the value is an Err, the Err will be returned from the whole function as if we had used the return keyword so the error value gets propagated to the calling code.
Und bitte nicht die Begriffe "Expression" und "Exception" verwechseln. Auch wenn sie ähnlich klingen haben sie nichts miteinander zu tun.
Das schwierigste beim Programmieren ist, sinnvolle Variablen- und Funktionsnamen zu finden :lol:
Sirius3
User
Beiträge: 18051
Registriert: Sonntag 21. Oktober 2012, 17:20

Oh, wie schön, dass es für jede Meinung ein passendes Video gibt. Verschieden Programmiersprachen verfolgen verschiedene Paradigmen. Jetzt ein anderes Paradigma einer Programmiersprache aufdrücken zu wollen, die sie nicht nativ unterstützt, ist falsch.
Wenn Dir Exceptions nicht gefallen, dann suche eine Programmiersprache, die das nicht unterstützt.
Inhaltlich kann man sowohl mit explizitem Error-Checking als auch mit Exceptions sauberen Code schreiben, oder eben das genaue Gegenteil. Mit beiden Mitteln kann man falsch auf Fehler reagieren. Und im Gegensatz zu dem was das Video behauptet, muß man in beiden Paradigmen ein weiteres Gleis legen, wenn man Fehler behandeln will. In Rust ist die Fehlerbehanldung eng mit dem normalen Code verwoben, so dass man unter Umständen schwerer zu lesenden Code bekommt, als wenn man mit except den Code, der Fehler behandelt, klar getrennt hat.
Noch schlimmer wird es dann mit ?, womit man des schlechte beider Welten hat, Verschleierung, wo der eigentliche Fehler auftritt und keine klare Trennung zwischen Nutzcode und Fehlerbehandlung. Hier zeigt sich, dass sauberes Programmieren zu umständlich ist, und man für Bequemlichkeit die Philosophie opfert.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Rust hat panics die man auch catchen kann und damit etwas dass Exceptions sehr ähnlich ist.
Noch schlimmer wird es dann mit ?, womit man des schlechte beider Welten hat, Verschleierung, wo der eigentliche Fehler auftritt und keine klare Trennung zwischen Nutzcode und Fehlerbehandlung. Hier zeigt sich, dass sauberes Programmieren zu umständlich ist, und man für Bequemlichkeit die Philosophie opfert.
Naja, ? macht schon explizit wo genau Fehler auftauchen können und ist auch nur syntax sugar für `match r { Ok(s) => s, Err(e) => return Err(e) }`, was man auch genau jedesmal schreiben könnte. Vor der Einführung von ? hat man dass auch überall gemacht. Der Fehler wird damit explizit weitergereicht an einen Ort wo er dann behandelt wird.

Das ? auch durchaus einen positiven Effekt hat sieht man auch deutlich bei Go, da bestehen dann Funktionen fast nur noch aus Code der Fehler durchreicht.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

burli hat geschrieben: Sonntag 11. September 2022, 09:19 Sorry, aber das ist völliger Käse.
Starke Worte fuer jemanden, der offensichtlich keine Ahnung hat, wie andere Sprachen Exceptions handhaben.
Aus einer Funktion wird nicht einfach heraus gesprungen wie bei einer Exception. Es gibt immer einen "geordneten Rückzug", soll heißen, ein Ausdruck liefert immer ein Ergebnis zurück. Entweder einen gültigen Wert oder einen Error Type, und zwar immer eingepackt in einem Result Type.
So funktionieren Exceptions in C++ - der Sprache, die hier in Bezug auf das Einsatzgebiet am relevantesten ist - auch schlicht nicht. Denn dort werden alle zum Zeitpunkt der Ausloesung der Ausnahme erzeugten frame-lokalen Variablen destruiert. Dieses Verhalten macht man sich unter anderem in dem RAII-Pattern zu Nutze, bei dem Resourcen trotz Ausnahmen (oder auch nur early returns, aber das ist in diesem Zusammenhang nicht unterscheidbar) wieder freigegeben werden. C kann das (ohne Erweiterungen, GCC hat da was) nicht, und entsprechend finden sich da goto-basierte Pattern, wie zb im Linux-Kernel allueberall: https://github.com/torvalds/linux/blob/ ... ack.c#L264 - durch Labels definiert man haendisch, welche Resourcen freigegeben werden muessen, wenn man ab einer bestimmten Stelle in einen Fehler laeuft. Und auf dieses Verhalten bezog sich meine Aussage. Das ganze kaskadiert natuerlich so lange, bis ein Handler gefunden wird, oder das Programm (ich denke mal von der crt) beendet wird.

Und ein solches Verhalten zeigt auch Rust, wenn man da den Drop-Trait nutzt. Freigabe trivialen Zustands ist natuerlich auch in C++ und C durch einfaches rueckbauen des Stacks gegeben.

Womit wir bei der Frage waeren, wie anders denn Exceptions vs einem Result-Typen sind. Und da ist die Antwort auch nicht so glasklar, wie du das hier behauptest. Typtheoretisch kann man Exceptions genauso als Summentyp aus der Menge der moeglichen Exceptions plus der Menge der moeglichen "normalen" Return-Werte betrachten. Was durch den algebraischen Typ Result in Rust abgebildet wird. Die Details dessen, wie denn nun genau Exceptions abgearbeitet werden, oder in Rust die Behandlung des Result-Typen erzwungen wird, ist fuer eine Frage danach, ob das nun goto sei oder nicht, irrelevant.
Um das Beispiel aus dem Video aufzugreifen: die num_parse() Funktion würde in Python entweder eine Zahl zurückgeben oder mit eine Exception beendet. Man springt also quasi mit einem Goto aus der Funktion an einen vorher definierten Punkt.
Wie ich dargelegt habe, ist dieses Verstaendnis im Kontext von anderen, statisch typisierten Sprachen ohne garbage collection, falsch. Was du beschreibst ist ein longjump, und auch wenn ich weiss, dass es den gibt, habe ich noch nie das Beduerfnis gehabt, ihn zu benutzen. Das sieht ein bisschen anders aus bei Sprachen wie Python oder Java. Letzteres kennt mit "checked Exceptions" einen Mechanismus, der aehnlich wie Rust das bearbeiten einer Ausnahme erzwingt. Das ist aber dermassen nervtoetend, dass selbst die Java runtime eine ganze Reihe von fundamentalen Ausnahmen als RuntimeException erklaert -weil man sonst vor lauter try/catch/rewrapt nichts mehr erkennen kann.

Und genau hier hat Rust etwas neues beigetragen: der ?-Operator. Der nicht syntaktischer Zucker fuer match ist, sondern fuer das try!-Macro, das zwar ein match enthaelt, aber dessen *zentrale* Aufgabe die Anwendung des From-Trait darstellt. Damit erst wird aus einer Muehsahl etwas ertraegliches - das implizite Wandeln von Fehlern aus tieferen Layern in Fehler die auf hoeheren Layern deklariert sind. Das ist toll, weil es an vielen Stellen die Lesbarkeit erhoeht, ohne die Nuetzlichkeit zu sehr einzuschraenken. Meistens. Aber eben nicht immer.

Denn Java und Python beherrschen hier einen Trick, den Rust nicht beherrscht: weil sie durch GC nicht damit belastet sind, schon im vorneherein Platz fuer eine ganze Kaskade von Fehlern zu reservieren, die beliebig tief sein kann, koennen sie eine Kette von Exceptions erzeugen, die einem eben nicht nur auf einer oberen Anwendungsebene sagt "SQL Connection failed", sondern auch noch die urspruengliche Exception mitliefert, die zeigt, dass es ein Netzwerkfehler war, oder ein Authentifizierungsfehler, oder was auch immer. Da sieht es mit Rust dann oede aus, und darauf hat sich auch Sirius3 bezogen, wenn er sagt, dass Rust etwas verschleiert.

Wir sind hier also mal wieder bei einer Frage der Abwaegung. Wenn dir Rust mehr zusagt, benutz es halt. Aber das es einen magischen Trick gefunden haette, Fehlerbehandlung gleichzeitig robuster und unobtrusiv gemacht zu haben, ist halt ... Kaese.
Und bitte nicht die Begriffe "Expression" und "Exception" verwechseln. Auch wenn sie ähnlich klingen haben sie nichts miteinander zu tun.
Puh. Haette ich das mal zu Zeiten meiner Diplomarbeit zur multi-Level-Typsystemen in funktionalen Sprachen gewusst, ich es waere noch mehr eine 1 geworden.
Antworten