it-swarm.com.de

Werte in der Funktionsprogrammierung "merken"

Ich habe beschlossen, die Aufgabe des Lernens der funktionalen Programmierung auf mich zu nehmen. Bisher war es eine tolle Zeit, und ich habe sozusagen das Licht gesehen. Leider kenne ich keinen funktionierenden Programmierer, von dem ich Fragen abwerfen kann. Einführung in Stack Exchange.

Ich nehme an einem Web-/Softwareentwicklungskurs teil, aber mein Lehrer ist mit funktionaler Programmierung nicht vertraut. Er kann es gut gebrauchen, und er hat mich nur gebeten, ihm zu helfen, zu verstehen, wie es funktioniert, damit er meinen Code besser lesen kann.

Ich entschied, dass der beste Weg, dies zu tun, darin besteht, eine einfache mathematische Funktion zu veranschaulichen, beispielsweise einen Wert auf eine Potenz zu erhöhen. Theoretisch könnte ich das leicht mit einer vorgefertigten Funktion machen, aber das würde den Zweck eines Beispiels zunichte machen.

Wie auch immer, ich habe einige Schwierigkeiten herauszufinden, wie man einen Wert hält. Da dies eine funktionale Programmierung ist, kann ich die Variable nicht ändern. Wenn ich dies unbedingt codieren würde, würde es ungefähr so ​​aussehen:

(Das Folgende ist alles Pseudocode)

f(x,y) {
  int z = x;
  for(int i = 0, i < y; i++){
    x = x * z;
  }
  return x;
}

Bei der funktionalen Programmierung war ich mir nicht sicher. Das habe ich mir ausgedacht:

f(x,y,z){
  if z == 'null',
    f(x,y,x);
  else if y > 1,
    f(x*z,y-1,z);
  else
    return x;
}

Ist das richtig? Ich muss in beiden Fällen den Wert z halten, war mir aber nicht sicher, wie ich das in der Funktionsprogrammierung machen soll. Theoretisch funktioniert die Art und Weise, wie ich es gemacht habe, aber ich war mir nicht sicher, ob es „richtig“ war. Gibt es einen besseren Weg, dies zu tun?

20
Ucenna

Zunächst einmal herzlichen Glückwunsch zum "Sehen des Lichts". Sie haben die Software-Welt zu einem besseren Ort gemacht, indem Sie Ihren Horizont erweitert haben.

Zweitens gibt es ehrlich gesagt keine Möglichkeit, dass ein Professor, der die funktionale Programmierung nicht versteht, etwas Nützliches über Ihren Code sagen kann, außer banalen Kommentaren wie "Die Einrückung sieht aus". Dies ist in einem Webentwicklungskurs nicht so überraschend, da die meisten Webentwicklungen mit HTML/CSS/JavaScript durchgeführt werden. Abhängig davon, wie sehr Sie sich tatsächlich für das Erlernen der Webentwicklung interessieren, möchten Sie möglicherweise Anstrengungen unternehmen, um die Werkzeuge zu erlernen, die Ihr Professor lehrt (auch wenn dies schmerzhaft sein mag - ich weiß aus Erfahrung).

Um die angegebene Frage zu beantworten: Wenn Ihr Imperativcode eine Schleife verwendet, ist Ihr Funktionscode wahrscheinlich rekursiv.

(* raises x to the power of y *)
fun pow (x: real) (y: int) : real = 
    if y = 1 then x else x * (pow x (y-1))

Beachten Sie, dass dieser Algorithmus tatsächlich mehr oder weniger mit dem imperativen Code identisch ist. Tatsächlich könnte man die obige Schleife als syntaktischen Zucker für iterative rekursive Prozesse betrachten.

Nebenbei bemerkt, es ist weder in Ihrem Imperativ- noch in Ihrem Funktionscode ein Wert von z erforderlich. Sie sollten Ihre imperative Funktion so geschrieben haben:

def pow(x, y):
    var ret = 1
    for (i = 0; i < y; i++)
         ret = ret * x
    return ret

anstatt die Bedeutung der Variablen x zu ändern.

37
gardenhead

Dies ist wirklich nur ein Nachtrag zu Gardenheads Antwort, aber ich möchte darauf hinweisen, dass es einen Namen für das Muster gibt, das Sie sehen: Falten.

In der funktionalen Programmierung ist a fold eine Möglichkeit, eine Reihe von Werten zu kombinieren, die sich einen Wert zwischen den einzelnen Operationen "merken". Erwägen Sie unbedingt das Hinzufügen einer Liste mit Zahlen:

def sum_all(xs):
  total = 0
  for x in xs:
    total = total + x
  return total

Wir nehmen eine Liste von Werten xs und ein Anfangszustand von 0 (in diesem Fall dargestellt durch total). Dann kombinieren wir für jedes x in xs diesen Wert mit dem aktueller Zustand gemäß einigen Kombinationsoperation (in diesem Fall Addition) ) und verwenden Sie das Ergebnis als ne Status. Im Wesentlichen entspricht sum_all([1, 2, 3])(3 + (2 + (1 + 0))). Dieses Muster kann in eine Funktion höherer Ordnung extrahiert werden, eine Funktion, die Funktionen als Argumente akzeptiert:

def fold(items, initial_state, combiner_func):
  state = initial_state
  for item in items:
    state = combiner_func(item, state)
  return state

def sum_all(xs):
  return fold(xs, 0, lambda x y: x + y)

Diese Implementierung von fold ist immer noch unerlässlich, kann aber auch rekursiv durchgeführt werden:

def fold_recursive(items, initial_state, combiner_func):
  if not is_empty(items):
    state = combiner_func(initial_state, first_item(items))
    return fold_recursive(rest_items(items), state, combiner_func)
  else:
    return initial_state

In Form einer Falte ausgedrückt, ist Ihre Funktion einfach:

def exponent(base, power):
  return fold(repeat(base, power), 1, lambda x y: x * y))

... wobei repeat(x, n) eine Liste von n Kopien von x zurückgibt.

Viele Sprachen, insbesondere solche, die auf funktionale Programmierung ausgerichtet sind, bieten das Falten in ihrer Standardbibliothek an. Sogar Javascript bietet es unter dem Namen reduce an. Wenn Sie Rekursion verwenden, um sich einen Wert über eine Schleife hinweg zu "merken", möchten Sie im Allgemeinen wahrscheinlich eine Falte.

33
Jack

Dies ist eine ergänzende Antwort, um Karten und Falten zu erklären. Für die folgenden Beispiele werde ich diese Liste verwenden. Denken Sie daran, dass diese Liste unveränderlich ist und sich daher niemals ändern wird:

var numbers = [1, 2, 3, 4, 5]

In meinen Beispielen werde ich Zahlen verwenden, da diese zu leicht lesbarem Code führen. Denken Sie jedoch daran, dass Falten für alles verwendet werden können, wofür eine herkömmliche Imperativschleife verwendet werden kann.

A map nimmt eine Liste von etwas und einer Funktion und gibt eine Liste zurück, die mit der Funktion geändert wurde. Jedes Element wird an die Funktion übergeben und wird zu dem, was die Funktion zurückgibt.

Das einfachste Beispiel hierfür ist das Hinzufügen einer Nummer zu jeder Nummer in einer Liste. Ich werde Pseudocode verwenden, um es sprachunabhängig zu machen:

function add-two(n):
    return n + 2

var numbers2 =
    map(add-two, numbers) 

Wenn Sie numbers2, du würdest sehen [3, 4, 5, 6, 7] Dies ist die erste Liste, in der jedem Element 2 hinzugefügt werden. Beachten Sie die Funktion add-two wurde map zur Verwendung gegeben.

Fold s sind ähnlich, außer dass die Funktion, die Sie ihnen geben müssen, 2 Argumente annehmen muss. Das erste Argument ist normalerweise der Akkumulator (in einer linken Falte, die am häufigsten vorkommt). Der Akkumulator sind die Daten, die während der Schleife übergeben werden. Das zweite Argument ist das aktuelle Element der Liste. genau wie oben für die Funktion map.

function add-together(n1, n2):
    return n1 + n2

var sum =
    fold(add-together, 0, numbers)

Wenn Sie sum drucken, wird die Summe der Zahlenliste angezeigt: 15.

Hier sind die Argumente für fold:

  1. Dies ist die Funktion, die wir die Falte geben. Die Falte übergibt die Funktion des aktuellen Akkumulators und des aktuellen Elements der Liste. Was auch immer die Funktion zurückgibt, wird zum neuen Akkumulator, der beim nächsten Mal an die Funktion übergeben wird. So "merken" Sie sich Werte, wenn Sie einen FP-Stil durchlaufen. Ich habe ihm eine Funktion gegeben das nimmt 2 Zahlen und fügt sie hinzu.

  2. Dies ist der anfängliche Akkumulator. Wie der Akku startet, bevor Elemente in der Liste verarbeitet werden. Wie hoch ist die Summe beim Summieren von Zahlen, bevor Sie Zahlen addiert haben? 0, die ich als zweites Argument übergeben habe.

  3. Schließlich übergeben wir wie bei der Karte auch die Liste der Nummern, die verarbeitet werden sollen.

Wenn Falten immer noch keinen Sinn ergeben, ziehen Sie dies in Betracht. Wenn Sie schreiben:

# Notice I passed the plus operator directly this time, 
#  instead of wrapping it in another function. 
fold(+, 0, numbers)

Grundsätzlich setzen Sie die übergebene Funktion zwischen die einzelnen Elemente in der Liste und fügen den anfänglichen Akkumulator entweder links oder rechts hinzu (je nachdem, ob es sich um eine linke oder rechte Falte handelt).

[1, 2, 3, 4, 5]

Wird:

0 + 1 + 2 + 3 + 4 + 5
^ Note the initial accumulator being added onto the left (for a left fold).

Welches entspricht 15.

Verwenden Sie ein map, wenn Sie eine Liste in eine andere Liste derselben Länge umwandeln möchten.

Verwenden Sie ein fold, wenn Sie eine Liste in einen einzelnen Wert verwandeln möchten, z. B. eine Summe von Zahlen summieren.

Wie @Jorg in den Kommentaren hervorhob, muss der "Einzelwert" nicht einfach wie eine Zahl sein. Es kann sich um ein einzelnes Objekt handeln, einschließlich einer Liste oder eines Tupels! Die Art und Weise, wie ich tatsächlich Falten klicken ließ, bestand darin, eine Karte als Falte zu definieren. Beachten Sie, wie der Akku eine Liste ist:

function map(f, list):
    fold(
        function(xs, x): # xs is the list that has been processed so far
            xs.add( f(x) ) # Add returns the list instead of mutating it
        , [] # Before any of the list has been processed, we have an empty list
        , list) 

Wenn Sie alle verstanden haben, werden Sie feststellen, dass fast jede Schleife durch eine Falte oder eine Karte ersetzt werden kann.

8
Carcigenicate

Es ist schwer, gute Probleme zu finden, die mit eingebauten Funktionen nicht gelöst werden können. Und wenn es eingebaut ist, sollte es als Beispiel für einen guten Stil in Sprache x verwendet werden.

In haskell zum Beispiel haben Sie bereits die Funktion (^) Im Prelude.

Oder wenn Sie es programmatischer machen möchten product (replicate y x)

Ich sage, dass es schwierig ist, die Stärken eines Stils/einer Sprache zu zeigen, wenn Sie die darin enthaltenen Funktionen nicht verwenden. Es mag jedoch ein guter Schritt sein, um zu zeigen, wie es hinter den Kulissen funktioniert, aber ich denke, Sie sollten den besten Weg in der von Ihnen verwendeten Sprache codieren und dann der Person von dort helfen, zu verstehen, was bei Bedarf vor sich geht.

1
Viktor Mellgren