Warum wird von der Verwendung von Zuweisungsoperatoren oder Schleifen bei der funktionalen Programmierung abgeraten?

9

Wenn meine Funktion die folgenden zwei Anforderungen erfüllt, kann die Funktion Sum , die die Summe der Elemente in einer Liste zurückgibt, in der das Element für eine bestimmte Bedingung als wahr ausgewertet wird, als reine Funktion bezeichnet werden, nicht wahr ?

1) Für einen gegebenen Satz von i / p wird derselbe o / p unabhängig von der Zeit zurückgegeben, zu der die Funktion aufgerufen wird

2) Es hat keine Nebenwirkungen

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if(predicate(item)) result += item;
    return result;
}

Beispiel: Sum(x=>x%2==0, new List<int> {1,2,3,4,5...100});

Der Grund, warum ich diese Frage stelle, ist, dass ich fast überall Leute sehe, die raten, Zuweisungsoperatoren und Schleifen zu vermeiden, weil dies ein zwingender Programmierstil ist. Was kann also mit dem obigen Beispiel schief gehen, das Schleifen und Zuweisungsoperatoren im Kontext der Funktionsprogrammierung verwendet?

rahulaga_dev
quelle
1
Es hat keine Nebenwirkung - es hat Nebenwirkung, wenn die itemVariable in der Schleife mutiert.
Fabio
@ Fabio ok. Aber können Sie den Umfang der Nebenwirkungen näher erläutern?
rahulaga_dev

Antworten:

16

Was macht die funktionale Programmierung aus?

Die funktionale Programmierung ist grundsätzlich deklarativ . Sie sagen, was Ihr Ergebnis ist, anstatt es zu berechnen.

Werfen wir einen Blick auf die wirklich funktionale Implementierung Ihres Snippets. In Haskell wäre es:

predsum pred numbers = sum (filter pred numbers)

Ist klar, was das Ergebnis ist? Es ist also die Summe der Zahlen, die dem Prädikat entsprechen. Wie wird es berechnet? Es ist mir egal, frag den Compiler.

Man könnte möglicherweise sagen, dass die Verwendung von sumund filterein Trick ist und nicht zählt. Lassen Sie es dann ohne diese Helfer implementieren (obwohl der beste Weg wäre, sie zuerst zu implementieren).

Die "Functional Programming 101" -Lösung, die nicht verwendet wird, sumist mit Rekursion:

sum pred list = 
    case list of
        [] -> 0
        h:t -> if pred h then h + sum pred t
                         else sum pred t

Es ist immer noch ziemlich klar, was das Ergebnis in Bezug auf einen einzelnen Funktionsaufruf ist. Es ist entweder 0oder recursive call + h or 0abhängig davon pred h. Immer noch ziemlich einfach, auch wenn das Endergebnis nicht sofort offensichtlich ist (obwohl dies mit ein wenig Übung wirklich wie eine forSchleife liest ).

Vergleichen Sie das mit Ihrer Version:

public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
    int result = 0;
    foreach(var item in numbers)
        if (predicate(item)) result += item;
    return result;
}

Was ist das Ergebnis? Oh, ich verstehe: einzelne returnAussage, keine Überraschungen hier : return result.

Aber was ist das result? int result = 0? Scheint nicht richtig. Damit machst du später etwas 0. Ok, du fügst items hinzu. Und so weiter.

Für die meisten Programmierer ist dies natürlich ziemlich offensichtlich, was in einer einfachen Funktion wie dieser passiert, aber fügen Sie eine zusätzliche returnAussage hinzu, und es wird plötzlich schwieriger zu verfolgen. Im gesamten Code geht es darum, wie und was der Leser noch herausfinden muss - dies ist eindeutig ein sehr zwingender Stil .

Sind also Variablen und Schleifen falsch?

Nein.

Es gibt viele Dinge, die durch sie viel einfacher erklärt werden, und viele Algorithmen, die einen veränderlichen Zustand erfordern, um schnell zu sein. Variablen sind jedoch von Natur aus unerlässlich. Sie erklären, wie statt was und geben nur wenige Vorhersagen darüber, wie hoch ihr Wert einige Zeilen später oder nach einigen Schleifeniterationen sein kann. Schleifen erfordern im Allgemeinen einen Zustand, um einen Sinn zu ergeben, und daher sind sie von Natur aus auch zwingend erforderlich.

Variablen und Schleifen sind einfach keine funktionale Programmierung.

Zusammenfassung

Die zeitgemäße funktionale Programmierung ist ein bisschen mehr Stil und eine nützliche Denkweise als ein Paradigma. In dieser Denkweise liegt eine starke Präferenz für die reinen Funktionen, aber es ist eigentlich nur ein kleiner Teil.

In den meisten weit verbreiteten Sprachen können Sie einige funktionale Konstrukte verwenden. In Python können Sie beispielsweise wählen zwischen:

result = 0
for num in numbers:
    if pred(result):
        result += num
return result

oder

return sum(filter(pred, numbers))

oder

return sum(n for n in numbers if pred(n))

Diese funktionalen Ausdrücke passen gut zu solchen Problemen und machen den Code einfach kürzer (und kürzer ist gut ). Sie sollten imperativen Code nicht gedankenlos durch sie ersetzen, aber wenn sie passen, sind sie fast immer die bessere Wahl.

Frax
quelle
Danke für die nette Erklärung !!
rahulaga_dev
1
@RahulAgarwal Diese Antwort mag interessant sein. Sie fängt ein tangentiales Konzept der Wahrheitsfindung und der Beschreibung der Schritte ein. Ich mag auch den Ausdruck "deklarative Sprachen enthalten Nebenwirkungen, imperative Sprachen nicht" - normalerweise haben funktionale Programme saubere und sehr sichtbare Schnitte zwischen dem zustandsbehafteten Code, der sich mit der Außenwelt befasst (oder einen optimierten Algorithmus ausführt), und rein funktionalem Code.
Frax
1
@Frax: danke !! Ich werde es mir ansehen. Ebenfalls kürzlich stieß Rich Hickey auf einen Vortrag über den Wert von Werten. Es ist wirklich brillant. Ich denke, eine Daumenregel - "arbeite mit Werten und Ausdruck, anstatt mit etwas zu arbeiten, das Wert hält und sich ändern kann"
rahulaga_dev
1
@Frax: Es ist auch fair zu sagen, dass FP eine Abstraktion über imperative Programmierung ist - weil letztendlich jemand die Maschine anweisen muss, wie es geht, oder? Wenn ja, hat die zwingende Programmierung dann im Vergleich zu FP nicht eine niedrigere Kontrolle auf niedriger Ebene?
rahulaga_dev
1
@Frax: Ich würde Rahul zustimmen, dass der Imperativ in dem Sinne niedriger ist, dass er näher an der zugrunde liegenden Maschine liegt. Wenn Hardware kostenlos Kopien von Daten erstellen könnte, wären keine zerstörerischen Updates erforderlich, um die Effizienz zu verbessern. In diesem Sinne ist das imperative Paradigma näher am Metall.
Giorgio
9

Von der Verwendung eines veränderlichen Zustands wird bei der funktionalen Programmierung im Allgemeinen abgeraten. Schleifen werden daher nicht empfohlen, da Schleifen nur in Kombination mit einem veränderlichen Zustand nützlich sind.

Die Funktion als Ganzes ist rein, was großartig ist, aber das Paradigma der funktionalen Programmierung gilt nicht nur auf der Ebene ganzer Funktionen. Sie möchten auch einen veränderlichen Status auch auf lokaler Ebene innerhalb von Funktionen vermeiden. Und die Argumentation ist im Grunde die gleiche: Das Vermeiden eines veränderlichen Zustands erleichtert das Verständnis des Codes und verhindert bestimmte Fehler.

In Ihrem Fall könnten Sie schreiben, numbers.Where(predicate).Sum()was eindeutig viel einfacher ist. Und einfacher bedeutet weniger Fehler.

JacquesB
quelle
Vielen Dank !! Ich glaube, mir fehlte eine markante Linie - aber das Paradigma der funktionalen Programmierung gilt nicht nur auf der Ebene ganzer Funktionen, sondern ich frage mich jetzt auch, wie ich diese Grenze visualisieren kann. Grundsätzlich ist es aus Verbrauchersicht reine Funktion, aber Entwickler, die diese Funktion tatsächlich geschrieben haben, haben die Richtlinien für reine Funktionen nicht befolgt? verwirrt :(
rahulaga_dev
@ RahulAgarwal: Welche Grenze?
JacquesB
Ich bin in gewissem Sinne verwirrt, wenn das Programmierparadigma aus Sicht des Funktionskonsumenten als FP qualifiziert ist. Bcoz, wenn ich mir die Implementierung der Implementierung von Wherein ansehe numbers.Where(predicate).Sum()- es nutzt die foreachSchleife.
rahulaga_dev
3
@RahulAgarwal: Als Konsument einer Funktion ist es Ihnen egal, ob eine Funktion oder ein Modul intern einen veränderlichen Zustand verwendet, solange er extern rein ist.
JacquesB
7

Während Sie richtig sind, dass aus Sicht eines externen Beobachters Ihre SumFunktion rein ist, ist die interne Implementierung eindeutig nicht rein - Sie haben einen Zustand gespeichert, in resultdem Sie wiederholt mutieren. Einer der Gründe , wandelbar Zustand zu vermeiden ist , weil es eine größere kognitive Belastung auf dem Programmiergerät erzeugt, was wiederum führt zu mehr Fehlern [Bearbeiten] .

Während in einem einfachen Beispiel wie diesem die Menge des gespeicherten veränderlichen Zustands wahrscheinlich klein genug ist, um keine ernsthaften Probleme zu verursachen, gilt das allgemeine Prinzip weiterhin. Ein Spielzeugbeispiel wie Sumwahrscheinlich ist nicht der beste Weg, um den Vorteil der funktionalen Programmierung gegenüber dem Imperativ zu veranschaulichen. Versuchen Sie, etwas mit viel veränderlichem Zustand zu tun, und die Vorteile werden möglicherweise klarer.

Philip Kendall
quelle