Pensée fonctionnelle. Partie 8

Bonjour, Habr! Nous revenons un peu en retard des vacances du Nouvel An avec la poursuite de notre série d'articles sur la programmation fonctionnelle. Aujourd'hui, nous allons parler de la compréhension des fonctions par le biais des signatures et de la définition de vos propres types de signatures de fonction. Détails sous la coupe!




Pas évident, mais F # a deux syntaxes: pour les expressions régulières (significatives) et pour les définitions de type. Par exemple:


[1;2;3] //   int list //   Some 1 //   int option //   (1,"a") //   int * string //   

Les expressions pour les types ont une syntaxe spéciale qui diffère de la syntaxe des expressions ordinaires. Vous avez peut-être remarqué de nombreux exemples de cette syntaxe en travaillant avec FSI (FSharp Interactive), comme les types de chaque expression sont affichés avec les résultats de son exécution.


Comme vous le savez, F # utilise un algorithme d'inférence de type, si souvent vous n'avez pas besoin d'écrire explicitement les types dans le code, en particulier dans les fonctions. Mais pour travailler efficacement avec F #, vous devez comprendre la syntaxe des types afin de pouvoir définir vos propres types, déboguer les erreurs de conversion de type et lire les signatures de fonction. Dans cet article, je me concentrerai sur l'utilisation des types dans les signatures de fonction.


Voici quelques exemples de signatures de syntaxe de type:


 //   //   let add1 x = x + 1 // int -> int let add xy = x + y // int -> int -> int let print x = printf "%A" x // 'a -> unit System.Console.ReadLine // unit -> string List.sum // 'a list -> 'a List.filter // ('a -> bool) -> 'a list -> 'a list List.map // ('a -> 'b) -> 'a list -> 'b list 

Comprendre les fonctions grâce aux signatures


Souvent, même en étudiant simplement la signature d'une fonction, vous pouvez avoir une idée de ce qu'elle fait. Considérez quelques exemples et analysez-les tour à tour.


 int -> int -> int 

Cette fonction prend deux paramètres int et renvoie un autre int . Il s'agit très probablement d'une sorte de fonctions mathématiques, telles que l'addition, la soustraction, la multiplication ou l'exponentiation.


 int -> unit 

Cette fonction prend un int et renvoie une unit , ce qui signifie que la fonction fait quelque chose d'important comme effet secondaire. Parce que il ne renvoie pas de valeur utile, l'effet secondaire produit très probablement des opérations d'écriture dans IO, telles que la journalisation, l'écriture dans la base de données ou quelque chose de similaire.


 unit -> string 

Cette fonction n'accepte rien, mais renvoie une string , ce qui peut signifier que la fonction reçoit une chaîne de l'air. Puisqu'il n'y a pas d'entrée explicite, la fonction fait probablement quelque chose avec la lecture (disons à partir d'un fichier) ou la génération (comme une chaîne aléatoire).


 int -> (unit -> string) 

Cette fonction prend un int et renvoie une autre fonction qui renverra une chaîne lorsqu'elle sera appelée. Encore une fois, probablement, la fonction effectue une opération de lecture ou de génération. L'entrée est susceptible d'initialiser en quelque sorte la fonction de retour. Par exemple, l'entrée peut être l'identifiant du fichier et la fonction renvoyée est similaire à readline() . Ou, l'entrée peut être la valeur de départ pour un générateur de chaînes aléatoires. Nous ne pouvons pas le dire avec certitude, mais nous pouvons tirer quelques conclusions.


 'a list -> 'a 

La fonction accepte une liste de tout type, mais ne renvoie qu'une seule valeur de ce type. Cela peut indiquer que la fonction agrège la liste ou sélectionne l'un de ses éléments. Une signature similaire est List.sum , List.max , List.head , etc.


 ('a -> bool) -> 'a list -> 'a list 

Cette fonction prend deux paramètres: la première est une fonction qui convertit quelque chose en bool (prédicat), la seconde est une liste. La valeur de retour est une liste du même type. Les prédicats sont utilisés pour déterminer si un objet répond à un certain critère et si cette fonction semble sélectionner des éléments d'une liste en fonction d'un prédicat - vrai ou faux. Après cela, il renvoie un sous-ensemble de la liste d'origine. Un exemple de fonction avec cette signature est List.filter .


 ('a -> 'b) -> 'a list -> 'b list 

La fonction prend deux paramètres: la conversion du type 'a au type 'b et une liste de type 'a . La valeur de retour est une liste de type 'b . Il est raisonnable de supposer que la fonction prend chaque élément de la liste 'a , et le convertit en 'b , en utilisant la fonction passée comme premier paramètre, puis retourne la liste 'b . En effet, List.map est le prototype d'une fonction avec une telle signature.


Recherche de méthodes de bibliothèque avec signatures


Les signatures de fonction sont très importantes pour trouver des fonctions de bibliothèque. Les bibliothèques F # contiennent des centaines de fonctions, ce qui peut être déroutant au début. Contrairement aux langages orientés objet, vous ne pouvez pas simplement «saisir un objet» via un point pour trouver toutes les méthodes associées. Mais si vous connaissez la signature de la fonction souhaitée, vous pouvez rapidement affiner votre recherche.


Par exemple, vous avez deux listes et vous souhaitez trouver une fonction en les combinant en une seule. Quelle signature aurait la fonction souhaitée? Il faudrait prendre deux listes comme paramètres et en retourner une troisième, toutes du même type:


 'a list -> 'a list -> 'a list 

Accédez maintenant au site de documentation MSDN du module List et recherchez une fonction similaire. Il s'avère qu'il n'y a qu'une seule fonction avec une telle signature:


 append : 'T list -> 'T list -> 'T list 

Ce dont vous avez besoin!


Définition de types natifs pour les signatures de fonction


Un jour, vous souhaiterez définir vos propres types pour la fonction souhaitée. Cela peut être fait en utilisant le mot-clé "type":


 type Adder = int -> int type AdderGenerator = int -> Adder 

À l'avenir, vous pouvez utiliser ces types pour limiter les valeurs des paramètres de fonction.


Par exemple, une deuxième déclaration tombera en raison d'une restriction avec une erreur de transtypage. Si nous le supprimons (comme dans la troisième annonce), l'erreur disparaîtra.


 let a:AdderGenerator = fun x -> (fun y -> x + y) let b:AdderGenerator = fun (x:float) -> (fun y -> x + y) let c = fun (x:float) -> (fun y -> x + y) 

Test de compréhension des signatures de fonction


Comprenez-vous bien les signatures de fonction? Vérifiez vous-même si vous pouvez créer des fonctions simples avec les signatures ci-dessous. Évitez de spécifier explicitement les types!


 val testA = int -> int val testB = int -> int -> int val testC = int -> (int -> int) val testD = (int -> int) -> int val testE = int -> int -> int -> int val testF = (int -> int) -> (int -> int) val testG = int -> (int -> int) -> int val testH = (int -> int -> int) -> int 

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/fr433402/


All Articles