Funktionsparadigma on Go: Grundtechniken



Hallo allerseits, wir erinnern euch daran, dass in diesem Monat in OTUS ein neues Set im Golang-Entwicklerkurs startet . Trotz des Hasses gegenüber dem vorherigen Artikel über Golang hat sich unser freiberuflicher Autor entschlossen, die Fortsetzung einer Reihe von Artikeln zu riskieren, die dieser Sprache gewidmet sind. Wir werden versuchen, wieder durch dieses dünne Eis zu gehen und uns dabei auf das zu verlassen, worauf Golang sich zu verlassen scheint - das funktionale Paradigma.



Wir erinnern Sie daran, dass dieser Artikel eine Art Material für "außerschulisches Lesen" ist und nicht mit dem Kursprogramm zusammenhängt, das hier zu finden ist .

Es ist klar, dass professionelle Programmierer in anderen Sprachen Golang anrufen
Irritation ist wie eine von Erwachsenen kompilierte Sprache, aber das Konzept von Klassen und Vererbung fehlt im Prinzip (obwohl OOP in einer Sprache implementiert ist, wenn auch auf ungewöhnliche Weise, durch ein System von Strukturen und Schnittstellen). Heute betrachten wir jedoch die Hauptimplementierungen bekannter Konstruktionen im Funktionsparadigma und versuchen, sowohl diese als auch die Sprachsyntax selbst zu erklären.



Jetzt gibt es viel Hype um das Funktionsparadigma (FP). Es ist aber auch kein Allheilmittel für alle Probleme und hat auch Vor- und Nachteile.

Kurz darüber, was ein Funktionsparadigma ist


Das Funktionsparadigma kam zur Programmierung aus der Mathematik. Es bildet folgende Voraussetzungen für das Programm:

  • Keine Änderung vorhandener Daten.
  • Es gibt keinen versteckten Zustand.

Was gibt uns das?

Unsere Funktionen arbeiten ohne Effekte von Drittanbietern. Mit anderen Worten, die Funktion sollte nur einen Wert zurückgeben und keine externen Daten beeinflussen.

Reine Funktionen nutzen. Sie erhöhen die Zuverlässigkeit von Funktionstests unabhängig von eingehenden Daten - mit anderen Worten, Programme werden zuverlässiger für Tests und ihre Ergebnisse werden vorhersehbarer.

Welche Möglichkeiten hat Golang, um das Funktionsparadigma umzusetzen:

Erstklassige Funktionen


Erstklassige Funktionen stehen in vielen Programmiersprachen zur Verfügung. Der Leser dieses Artikels kennt sein Konzept höchstwahrscheinlich bereits von so weit verbreitetem JavaScript, aber ich werde es noch einmal wiederholen. Die Funktionen der ersten Klasse (Funktion höherer Ordnung) sind Funktionen, die eine andere Funktion als Wissen zurückgeben, eine Funktion als Argument nehmen und den Wert der Funktion an eine andere Variable übergeben können.
Lassen Sie uns von Anfang an zustimmen : Um Platz zu sparen, habe ich die ersten beiden Zeilen des Codes, der hier vorgestellt wird, weggelassen: 'package main' und 'import' "fmt" "importieren. Um den Code auf Ihrem Computer auszuführen, müssen Sie ihn jedoch hinzufügen.


func main() { var list = []int{15, 16, 45, 34} //      var out = forEach(list, func(it int) int { //      //forEach   ""  return (it * it) //      }) fmt.Println(out) // [225, 256, 2025, 1156] fmt.Println(list) //      } func forEach(arr []int, fn func(it int) int) []int { //      ,   ,     var newArray = []int{} //     ""   for _, it := range arr { newArray = append(newArray, fn(it)) //      for } return newArray } 


In der Tat ist es überhaupt nicht notwendig, eine eigene map zu erfinden oder von Grund auf neu zu foreach . Es gibt viele Bibliotheken, die dies implementieren, es bleibt nur, um sie zu verbinden. Zum Beispiel dieses .

Verschlüsse und Currying-Funktionen


In vielen modernen Programmiersprachen gibt es Kurzschlüsse. Closures sind eine Funktion, die sich auf die freien Scope-Variablen der übergeordneten Funktion bezieht. Funktionscurrying ist ein Funktionswechsel von der Formularfunktion func(a,b,c) zur Formularfunktion func(a)(b)(c) .

Hier ist ein Beispiel für Schließungen und Currys in Go:

 //  func multiply(x int) func(y int) int { //    return func(y int) int { //   ,       JS return x * y } } func main() { //     var mult10 = multiply(10) var mult15 = multiply(15) fmt.Println(mult10(5)) //50 fmt.Println(mult15(15))//225 } 


Reine Funktionen


Wie bereits erwähnt, sind reine Funktionen diejenigen, die Werte zurückgeben, die nur mit eingehenden Argumenten verknüpft sind und den globalen Status nicht beeinflussen.

Hier ist ein Beispiel für eine fehlerhafte, fehlerhafte Funktion:

 var arrToSave = map[string]int{} //map -    -   Golang func dirtySum(a, b int) int { c := a + b arrToSave[fmt.Sprintf("%d", a, b)] = c //   ,  "%d" -       return c } 

Hier sollte unsere Funktion akzeptieren, so vorhersehbar wie möglich zu arbeiten:

 func simpleSum(x, y int) int { return x + y } func main() { fmt.Printf("%v", dirtySum(13, 12)) //      //   ""      fmt.Printf("%v", simpleSum(13, 12)) } 

"Irgendwie kommt Rekursion in die Bar und niemand sonst kommt in die Bar"
Aus der Sammlung von witzlosen Witzen.

Rekursion


In einem funktionalen Paradigma ist es üblich, der Rekursion den Vorzug zu geben - für Reinheit und Transparenz, anstatt eine einfache Iteration durch for .

Hier ist ein Beispiel für die Berechnung von Fakultäten unter Verwendung des imperativen und deklarativen Paradigmas:

 func funcFactorial(num int) int { if num == 0 { return 1 } return num * funcFactorial(num-1) } func imperativeFactorial(num int) int { var result int = 1 for ; num > 0; num-- { //    for result *= num } return result } func main() { fmt.Println(funcFactorial(20)) //        fmt.Println(imperativeFactorial(20)) //      } 


Jetzt arbeitet die Rekursionsfunktion ziemlich ineffizient. Versuchen wir es ein wenig umzuschreiben, um die Geschwindigkeit der Berechnung zu optimieren:

 func factTailRec(num int) int { return factorial(1, num) //    ""  } func factorial(accumulator, val int) int { if val == 1 { return accumulator } return factorial(accumulator*val, val-1) } func main() { fmt.Println(factTailRec(20)) // 2432902008176640000 } 


Unsere faktorielle Rechengeschwindigkeit hat sich leicht erhöht. Ich werde keine Benchmarks geben).

Leider implementiert Go keine sofort einsatzbereite Rekursionsoptimierung, sodass Sie das Rekursionsende selbst optimieren müssen. Obwohl ohne Zweifel eine nützliche Bibliothek zu diesem Thema gefunden werden kann. Zu diesem Thema gibt es zum Beispiel so einen „Loadash for Golang“.

Lazy Computing


In der Programmiertheorie ist Lazy Computing (auch als "Deferred Computing" bezeichnet) der Prozess, bei dem das Computing so lange verzögert wird, bis es benötigt wird. Golang bietet keine Unterstützung für Lazy Computing, daher können wir nur Folgendes simulieren:

 func mult(x, y int) int { fmt.Println(" ") return x * x. } func divide(x, y int) int { fmt.Println(" ") return x / y //    -  } func main() { fmt.Println(multOrDivide(true, mult, divide, 17, 3)) //   ""   ,   1  , //         fmt.Println(multOrDivide(false, mult, divide, 17, 3)) } //  if - else    ""  func multOrDivide(add bool, onMult, onDivide func(t, z int) int, t, z int) int { if add { return onMult(t, z) } return onDivide(t, z) } 


In den meisten Fällen lohnen sich "emulierte" langsame Ausdrücke nicht, da sie den Code übermäßig komplizieren. Wenn Ihre Funktionen jedoch schwierig zu verwalten sind, sollten Sie diese Methode verwenden. Sie können sich aber auch anderen Lösungen zuwenden, beispielsweise diesen .



Das ist alles. Wir haben nur eine Einführung in das Funktionsparadigma von Golang bekommen. Leider musste ein Teil der Möglichkeiten simuliert werden. Teilweise vollständig entwickelte Funktionstechniken, wie Monaden, wurden hier nicht berücksichtigt, da in Go on the hub eine Vielzahl von Artikeln zu diesen Themen enthalten ist Vieles kann noch in der Sprache selbst verbessert werden, zum Beispiel wird erwartet, dass die Generika der nächsten großen Version (GO 2) in der Sprache erscheinen. Nun, wir werden warten und hoffen.

Source: https://habr.com/ru/post/de476346/


All Articles