Paradigme fonctionnel sur Go: techniques de base



Bonjour à tous, nous vous rappelons que ce mois-ci dans OTUS un nouvel ensemble commencera au cours de développeur de Golang . Malgré une certaine haine de l'article précédent sur Golang, notre rédacteur indépendant a décidé de prendre le risque de poursuivre une série d'articles consacrés à cette langue. Nous allons essayer de traverser à nouveau cette mince couche de glace, en nous appuyant sur ce sur quoi Golang semble s'appuyer - le paradigme fonctionnel.



Nous vous rappelons que cet article est une sorte de matériel pour la "lecture parascolaire" et n'est pas lié au programme de cours, qui peut être trouvé ici .

Il est clair que des programmeurs professionnels dans d'autres langues appellent Golang
l'irritation est comme une langue adulte compilée, mais le concept de classes et d'héritage est absent en principe (bien que la POO soit implémentée dans une langue, quoique d'une manière plutôt inhabituelle, à travers un système de structures et d'interfaces). Cependant, aujourd'hui, nous examinons les principales implémentations de constructions familières dans le paradigme fonctionnel et essayons de les expliquer ainsi que la syntaxe du langage lui-même.



Maintenant, il y a beaucoup de battage médiatique autour du paradigme fonctionnel (FP). Cependant, ce n'est pas non plus une panacée pour tous les problèmes, et a aussi ses avantages et ses inconvénients.

En bref sur ce qu'est un paradigme fonctionnel


Le paradigme fonctionnel est venu à la programmation des mathématiques. Il forme les exigences suivantes pour le programme:

  • Aucun changement dans les données existantes.
  • Il n'y a pas d'état caché.

Qu'est-ce que cela nous donne?

Nos fonctions fonctionnent sans effets tiers. En d'autres termes, la fonction ne doit renvoyer qu'une valeur et ne doit affecter aucune donnée externe.

Utilisation de fonctionnalités pures. Ils augmentent la fiabilité du retest des fonctions quelles que soient les données entrantes - en d'autres termes, les programmes deviennent plus fiables pour les tests et leurs résultats deviennent plus prévisibles.

Alors, quelles sont les possibilités que Golang a pour mettre en œuvre le paradigme fonctionnel:

Fonctions de première classe


Les fonctions de première classe sont disponibles dans de nombreux langages de programmation. Le lecteur de cet article connaît probablement déjà son concept à partir d'un JavaScript aussi répandu, mais je le répéterai à nouveau. Les fonctions de la première classe (fonction d'ordre supérieur) sont des fonctions qui peuvent renvoyer une autre fonction comme connaissance, prendre une fonction comme argument et transmettre la valeur de la fonction à une autre variable.
Soyons d'accord dès le début : pour économiser de l'espace, j'ai jeté les deux premières lignes du code présenté ici: 'package main' et import 'import "fmt"'. Mais pour exécuter le code sur votre machine, pensez à les ajouter).


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 } 


En fait, il n'est pas du tout nécessaire d'inventer votre propre map ou de foreach partir de zéro. Il existe de nombreuses bibliothèques qui implémentent cela, il ne reste plus qu'à les connecter. Par exemple, celui-ci .

Fermetures et fonctions de curry


Il existe des courts-circuits dans de nombreux langages de programmation modernes. Les fermetures sont une fonction qui fait référence aux variables de portée libre de sa fonction parent. Le curry de fonction est un changement de fonction de la forme func(a,b,c) à la forme func(a)(b)(c) .

Voici un exemple de fermetures et de curry dans 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 } 


Fonctions pures


Comme nous l'avons dit précédemment, les fonctions pures sont celles qui renvoient des valeurs qui ne sont associées qu'à des arguments qui entrent et n'affectent pas l'état global.

Voici un exemple de fonction défaillante et sale:

 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 } 

Ici, notre fonction doit accepter de travailler de la manière la plus prévisible possible:

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

"D'une manière ou d'une autre, la récursivité entre dans le bar, et personne d'autre n'entre dans le bar"
De la collection de blagues drôles.

Récursivité


Dans un paradigme fonctionnel, il est habituel de donner la préférence à la récursivité - pour la pureté et la transparence, au lieu d'utiliser une simple itération à travers for .

Voici un exemple de calcul factoriel utilisant le paradigme impératif et déclaratif:

 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)) //      } 


Maintenant, la fonction de récursivité fonctionne de manière assez inefficace. Essayons de le réécrire un peu afin d'optimiser la vitesse de son calcul:

 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 } 


Notre vitesse de calcul factorielle a légèrement augmenté. Je ne donnerai pas de repères).

Malheureusement, Go n'implémente pas l'optimisation de récursivité dès le départ, vous devez donc optimiser vous-même la queue de récursivité. Bien que, sans aucun doute, une bibliothèque utile sur ce sujet puisse certainement être trouvée. Par exemple, il y a un tel "Loadash pour Golang" cool sur ce sujet .

Informatique paresseuse


Dans la théorie de la programmation, l'informatique paresseuse (également appelée «informatique différée») est le processus de différer l'informatique jusqu'à ce qu'elle soit nécessaire. Golang ne prend pas en charge l'informatique paresseuse dès la sortie de la boîte, nous ne pouvons donc que simuler ceci:

 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) } 


Le plus souvent, les expressions paresseuses «émulées» n'en valent pas la peine car elles compliquent trop le code, mais si vos fonctions sont assez difficiles à gérer, alors vous devriez utiliser cette méthode. Mais vous pouvez vous tourner vers d'autres solutions, par exemple, celles-ci .



C’est tout. Nous n'avons eu qu'une introduction au paradigme fonctionnel sur Golang. Malheureusement, une partie des possibilités a dû être simulée. Une partie, les techniques fonctionnelles entièrement développées, telles que les monades, n'ont pas été incluses ici, car il y a beaucoup d' articles à leur sujet dans Go on the hub Beaucoup peut encore être amélioré dans la langue elle-même, par exemple avec la prochaine grande version (GO 2), les génériques devraient apparaître dans la langue. Eh bien, nous attendrons et espérons).

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


All Articles