Werden veränderbare lokale Variablen in einer Funktion, die nur intern verwendet werden (z. B. die Funktion hat keine Nebenwirkungen, zumindest nicht absichtlich), immer noch als "nicht funktionsfähig" betrachtet?
zB bei der "Funktionsprogrammierung mit Scala" wird bei der Prüfung des Kursstils jede var
Verwendung als schlecht eingestuft
Meine Frage, ob die Funktion keine Nebenwirkungen hat, ist das Schreiben von imperativem Stilcode immer noch nicht empfehlenswert?
Was ist z. B. falsch daran, eine lokale for-Schleife zu erstellen, eine lokale Mutable zu erstellen ListBuffer
und zu addieren, anstatt die Tail-Rekursion mit dem Akkumulatormuster zu verwenden, solange die Eingabe nicht geändert wird?
Wenn die Antwort "Ja, sie sind immer entmutigt, auch wenn es keine Nebenwirkungen gibt", was ist dann der Grund?
quelle
var
ist immer nicht funktionsfähig. Scala verfügt über eine Lazy Vals- und Schwanzrekursionsoptimierung, die es ermöglicht, Vars vollständig zu vermeiden.Antworten:
Das Einzige, was hier eindeutig schlecht ist, ist die Behauptung, dass etwas eine reine Funktion ist, wenn es nicht so ist.
Wenn veränderbare Variablen in einer Weise verwendet werden, die wirklich und vollständig in sich geschlossen ist, ist die Funktion äußerlich rein und jeder ist glücklich. Haskell unterstützt dies ausdrücklich , wobei das Typsystem sogar sicherstellt, dass veränderbare Referenzen nicht außerhalb der Funktion verwendet werden können, die sie erstellt.
Trotzdem denke ich, dass das Sprechen über "Nebenwirkungen" nicht die beste Art ist, es zu betrachten (und deswegen habe ich oben "rein" gesagt). Alles, was eine Abhängigkeit zwischen der Funktion und dem externen Zustand erzeugt, erschwert das Nachdenken und beinhaltet Dinge wie die Kenntnis der aktuellen Zeit oder die Verwendung des verdeckten veränderlichen Zustands auf nicht thread-sichere Weise.
quelle
Das Problem ist nicht die Veränderbarkeit an sich, es ist ein Mangel an referentieller Transparenz.
Eine referenziell transparente Sache und eine Referenz darauf müssen immer gleich sein, so dass eine referenziell transparente Funktion für eine gegebene Menge von Eingaben immer die gleichen Ergebnisse zurückgibt und eine referenziell transparente "Variable" wirklich ein Wert und keine Variable ist, da sie kann mich nicht ändern. Sie können eine referenziell transparente Funktion erstellen, die eine veränderbare Variable enthält. das ist kein problem Es ist möglicherweise schwieriger zu gewährleisten, dass die Funktion referenziell transparent ist, je nachdem, was Sie tun.
Ich kann mir nur einen Fall vorstellen, bei dem die Veränderbarkeit genutzt werden muss, um etwas sehr Funktionsfähiges zu erreichen: das Auswendiglernen. Beim Speichern werden Werte aus einer Funktion zwischengespeichert, sodass sie nicht neu berechnet werden müssen. es ist referenziell transparent, aber es verwendet Mutation.
Aber im Allgemeinen gehören referenzielle Transparenz und Unveränderlichkeit zusammen, abgesehen von einer lokal veränderlichen Variablen in einer referenziell transparenten Funktion und Memoisierung. Ich bin mir nicht sicher, ob es andere Beispiele gibt, bei denen dies nicht der Fall ist.
quelle
Es ist nicht wirklich gut, dies auf "gute Praxis" oder "schlechte Praxis" zu reduzieren. Scala unterstützt veränderbare Werte, weil sie bestimmte Probleme viel besser lösen als unveränderbare Werte, nämlich solche, die iterativer Natur sind.
Für die Perspektive bin ich mir ziemlich sicher, dass über
CanBuildFrom
fast alle unveränderlichen Strukturen, die von scala bereitgestellt werden, eine Art von interner Mutation erfolgt. Der Punkt ist, dass das, was sie aussetzen, unveränderlich ist. Indem Sie so viele Werte wie möglich unveränderlich halten, können Sie leichter über das Programm nachdenken und sind weniger fehleranfällig .Dies bedeutet nicht, dass Sie veränderbare Strukturen und Werte intern unbedingt vermeiden müssen, wenn Sie ein Problem haben, das sich besser für die Veränderbarkeit eignet.
In Anbetracht dessen können viele Probleme, die normalerweise veränderbare Variablen erfordern (wie z. B. Schleifen), mit vielen Funktionen höherer Ordnung, die Sprachen wie Scala bereitstellen (map / filter / fold), besser gelöst werden. Sei dir dessen bewusst.
quelle
map
,filter
,foldLeft
UndforEach
der Trick die meiste Zeit, aber wenn sie es nicht tun, fühlen sich in der Lage, I „OK“ , um Rückkehr zu Brute - Force - Imperativ Code bin , ist schön. (solange es keine Nebenwirkungen gibt natürlich)Abgesehen von möglichen Problemen mit der Threadsicherheit verlieren Sie normalerweise auch viel Typensicherheit. Imperative Schleifen haben den Rückgabetyp
Unit
und können so ziemlich jeden Ausdruck für Eingaben annehmen. Funktionen höherer Ordnung und sogar Rekursion weisen eine viel genauere Semantik und genauere Typen auf.Sie haben auch viel mehr Optionen für die funktionale Containerverarbeitung als bei imperativen Schleifen. Mit zwingend notwendig, haben Sie grundsätzlich
for
,while
und kleinere Variationen dieser beiden wiedo...while
undforeach
.Im funktionalen Bereich haben Sie Aggregat, Zählen, Filtern, Finden, FlatMap, Falten, GroupBy, LastIndexWhere, Map, MaxBy, MinBy, Partition, Scan, SortBy, SortWith, Span und TakeWhile, um nur einige weitere gebräuchliche von Scala zu nennen Standardbibliothek. Wenn Sie sich daran gewöhnt haben, diese zur Verfügung zu haben, erscheinen imperative
for
Schleifen im Vergleich zu grundlegend.Der einzige wirkliche Grund, die lokale Veränderlichkeit zu nutzen, ist gelegentlich die Leistung.
quelle
Ich würde sagen, dass es größtenteils in Ordnung ist. Darüber hinaus kann das Generieren von Strukturen in einigen Fällen eine gute Möglichkeit sein, die Leistung zu verbessern. Clojure hat dieses Problem durch die Bereitstellung transienter Datenstrukturen gelöst .
Die Grundidee besteht darin, lokale Mutationen in einem begrenzten Bereich zuzulassen und die Struktur dann einzufrieren, bevor sie zurückgegeben wird. Auf diese Weise kann Ihr Benutzer Ihren Code immer noch als reinen Code betrachten, aber Sie können bei Bedarf Transformationen an Ort und Stelle durchführen.
Wie der Link sagt:
quelle
Keine lokalen veränderbaren Variablen zu haben, hat einen Vorteil - es macht die Funktion gegenüber Threads freundlicher.
Ich habe mich durch eine solche lokale Variable verbrannt (weder in meinem Code noch in der Quelle), was zu einer Datenbeschädigung mit geringer Wahrscheinlichkeit geführt hat. Thread-Sicherheit wurde auf die eine oder andere Weise nicht erwähnt, es gab keinen Zustand, der über Anrufe hinweg bestand, und es gab keine Nebenwirkungen. Es ist mir nicht in den Sinn gekommen, dass es möglicherweise nicht threadsicher ist. Die Jagd nach einer 1 in 100.000 zufälligen Datenkorruption ist ein königlicher Schmerz.
quelle