Pensée fonctionnelle. Partie 6

Nous continuons notre série d'articles sur la programmation fonctionnelle en F #. Aujourd'hui, nous parlons de l'associativité et de la composition des fonctions, ainsi que de comparer la composition et le pipeline. Regardez sous le chat!




Associativité et composition des fonctions


Associativité des fonctions


Supposons qu'il existe une chaîne de fonctions écrites dans une rangée. Dans quel ordre seront-ils combinés?


Par exemple, que signifie cette fonction?


let F xyz = xyz 

Est-ce à dire que la fonction y doit être appliquée à l'argument z , puis le résultat doit être passé à x ? C'est-à-dire:


 let F xyz = x (yz) 

Ou la fonction x appliquée à l'argument y , après quoi la fonction obtenue en conséquence sera évaluée avec l'argument z ? C'est-à-dire:


 let F xyz = (xy) z 

  1. La deuxième option est correcte.
  2. L'utilisation des fonctions a laissé l'associativité .
  3. xyz signifie la même chose que (xy) z .
  4. Et wxyz est égal à ((wx) y) z .
  5. Cela ne devrait pas être génial.
  6. Nous avons déjà vu comment fonctionne une application partielle.
  7. Si nous parlons de x comme une fonction à deux paramètres, alors (xy) z est le résultat d'une application partielle du premier paramètre, suivi du passage de l'argument z à la fonction intermédiaire.

Si vous avez besoin d'une bonne associativité, vous pouvez utiliser des parenthèses ou des tuyaux. Les trois entrées suivantes sont équivalentes:


 let F xyz = x (yz) let F xyz = yz |> x //    let F xyz = x <| yz //    

En guise d'exercice, essayez d'afficher les signatures de ces fonctions sans calcul réel.


Composition des fonctions


Nous avons mentionné la composition des fonctions à plusieurs reprises, mais que signifie vraiment ce terme? Cela semble effrayant à première vue, mais en fait, tout est assez simple.


Supposons que nous ayons une fonction "f" qui mappe le type "T1" au type "T2". Nous avons également une fonction «g» qui convertit le type «T2» en type «T3». Ensuite, nous pouvons connecter la sortie de "f" et l'entrée de "g", créant une nouvelle fonction qui convertit le type "T1" en type "T3".



Par exemple:


 let f (x:int) = float x * 3.0 // f  -  int->float let g (x:float) = x > 4.0 // g  -  float->bool 

Nous pouvons créer une nouvelle fonction "h" qui prend la sortie de "f" et l'utilise comme entrée pour "g".


 let h (x:int) = let y = f(x) g(y) //    g 

Un peu plus compact:


 let h (x:int) = g ( f(x) ) // h    int->bool // h 1 h 2 

Jusqu'à présent, si simple. C'est intéressant, on peut définir une nouvelle fonction "composer", qui prend les fonctions "f" et "g" et les combine sans même connaître leurs signatures.


 let compose fgx = g ( f(x) ) 

Après l'exécution, vous pouvez voir que le compilateur a correctement décidé que " f " est une fonction du type générique 'a au type générique 'b , et ' g ' est limité à l'entrée de type 'b :


 val compose : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c 

(Notez qu'une composition généralisée des opérations n'est possible que parce que chaque fonction a exactement un paramètre d'entrée et une sortie. Cette approche n'est pas possible dans les langages non fonctionnels.)


Comme nous pouvons le voir, cette définition est utilisée pour l'opérateur " >> ".


 let (>>) fgx = g ( f(x) ) 

Grâce à cette définition, de nouvelles fonctions peuvent être construites sur la base de fonctions existantes utilisant la composition.


 let add1 x = x + 1 let times2 x = x * 2 let add1Times2 x = (>>) add1 times2 x // add1Times2 3 

L'enregistrement explicite est très lourd. Mais vous pouvez rendre son utilisation plus facile à comprendre.


Tout d'abord, vous pouvez vous débarrasser du paramètre x et la composition renverra une application partielle.


 let add1Times2 = (>>) add1 times2 

Deuxièmement, parce que >> est un opérateur binaire, vous pouvez le mettre au centre.


 let add1Times2 = add1 >> times2 

L'utilisation de la composition rend le code plus propre et plus clair.


 let add1 x = x + 1 let times2 x = x * 2 //   let add1Times2 x = times2(add1 x) //   let add1Times2 = add1 >> times2 

Utilisation de l'opérateur de composition en pratique


L'opérateur de composition (comme tous les opérateurs d'infixe) a une priorité inférieure aux fonctions normales. Cela signifie que les fonctions utilisées dans la composition peuvent avoir des arguments sans utiliser de parenthèses.


Par exemple, si les fonctions "add" et "times" ont des paramètres, ils peuvent être passés pendant la composition.


 let add nx = x + n let times nx = x * n let add1Times2 = add 1 >> times 2 let add5Times3 = add 5 >> times 3 // add5Times3 1 

Tant que les entrées et sorties correspondantes des fonctions correspondent, les fonctions peuvent utiliser n'importe quelle valeur. Par exemple, considérez le code suivant qui exécute une fonction deux fois:


 let twice f = f >> f // ('a -> 'a) -> ('a -> 'a) 

Notez que le compilateur a déduit que " f " accepte et renvoie des valeurs du même type.


Considérons maintenant la fonction " + ". Comme nous l'avons vu précédemment, l'entrée est int , mais la sortie est en fait (int->int) . Ainsi, " + " peut être utilisé en " twice ". Par conséquent, vous pouvez écrire:


 let add1 = (+) 1 //  (int -> int) let add1Twice = twice add1 //    (int -> int) // add1Twice 9 

En revanche, vous ne pouvez pas écrire:


 let addThenMultiply = (+) >> (*) 

Parce que l'entrée "*" doit être une fonction int , pas une fonction int->int (qui est la sortie de l'addition).


Mais si vous corrigez la première fonction pour qu'elle ne renvoie que int , tout fonctionnera:


 let add1ThenMultiply = (+) 1 >> (*) // (+) 1   (int -> int)   'int' // add1ThenMultiply 2 7 

La composition peut également être réalisée dans l'ordre inverse au moyen de " << ", si nécessaire:


 let times2Add1 = add 1 << times 2 times2Add1 3 

La composition inversée est principalement utilisée pour rendre le code plus semblable à l'anglais ("semblable à l'anglais"). Par exemple:


 let myList = [] myList |> List.isEmpty |> not //   myList |> (not << List.isEmpty) //    

Composition vs convoyeur


Vous pouvez être confus par la petite différence entre la composition et le convoyeur, car ils peuvent sembler très similaires.


Tout d'abord, regardez la définition d'un pipeline:


 let (|>) xf = fx 

Tout cela vous permet de placer les arguments des fonctions avant et non après. C’est tout. Si la fonction a plusieurs paramètres, l'entrée doit être le dernier paramètre (dans l'ensemble de paramètres actuel, et pas du tout). Un exemple vu plus tôt:


 let doSomething xyz = x+y+z doSomething 1 2 3 //      3 |> doSomething 1 2 //      

La composition n'est pas la même et ne peut pas remplacer un tuyau. Dans l'exemple suivant, même le nombre 3 n'est pas une fonction, donc la "sortie" ne peut pas être passée à doSomething :


 3 >> doSomething 1 2 //  // f >> g      g(f(x))     : doSomething 1 2 ( 3(x) ) //   3   ! // error FS0001: This expression was expected to have type 'a->'b // but here has type int 

Le compilateur se plaint que la valeur "3" doit être une sorte de fonction 'a->'b .


Comparez cela à la définition de la composition, qui prend 3 arguments, où les deux premiers devraient être des fonctions.


 let (>>) fgx = g ( f(x) ) let add nx = x + n let times nx = x * n let add1Times2 = add 1 >> times 2 

Les tentatives d'utilisation du pipeline au lieu de la composition entraîneront une erreur de compilation. Dans l'exemple suivant, " add 1 " est la fonction (partielle) int->int , qui ne peut pas être utilisée comme deuxième paramètre pour " times 2 ".


 let add1Times2 = add 1 |> times 2 //  // x |> f      f(x)     : let add1Times2 = times 2 (add 1) // add1   'int' // error FS0001: Type mismatch. 'int -> int' does not match 'int' 

Le compilateur se plaint que " times 2 " doit accepter le paramètre int->int , c'est-à-dire être une fonction (int->int)->'a .


Ressources supplémentaires


Il existe de nombreux didacticiels pour F #, y compris des documents pour ceux qui viennent avec une expérience C # ou Java. Les liens suivants peuvent être utiles pour approfondir F #:



Plusieurs autres façons de commencer à apprendre le F # sont également décrites.


Enfin, la communauté F # est très conviviale pour les débutants. Il y a un chat très actif chez Slack, soutenu par la F # Software Foundation, avec des salles pour débutants que vous pouvez rejoindre librement . Nous vous recommandons fortement de le faire!


N'oubliez pas de visiter le site de la communauté russophone F # ! Si vous avez des questions sur l'apprentissage d'une langue, nous serons heureux d'en discuter dans les salles de chat:



À propos des auteurs de traduction


Traduit par @kleidemos
La traduction et les modifications éditoriales ont été apportées par les efforts de la communauté russophone des développeurs F # . Nous remercions également @schvepsss et @shwars d' avoir préparé cet article pour publication.

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


All Articles