Swift fonctionnel

Qu'est-ce qui unit «curry», «monades», «types de données algébriques»? Non seulement le fait que certains développeurs tentent de contourner ces mots, mais aussi la programmation fonctionnelle. Sous la direction attentive d'Evgeny Elchev, nous avons plongé dans un paradigme fonctionnel et compris presque tout. N'ayez pas peur à l'avance, n'hésitez pas à lire la transcription de la dixième édition du podcast AppsCast .



Daniil Popov: Bonjour à tous. Aujourd'hui, notre invité est Evgeny Elchev de Krasnoyarsk ensoleillé. Eugene, dites-moi ce que vous faites et comment vous êtes arrivé à la programmation fonctionnelle?

Evgeny Elchev: Bonjour à tous. Je suis développeur iOS chez Redmadrobot, comme tout le monde, je peins des boutons, parfois j'écris de la logique métier.

J'ai d'abord découvert la programmation fonctionnelle à travers des articles. Je, ne comprenant pas très bien le point, pensais que c'était une sorte de programmation procédurale, sans classes. Quand j'ai lu l'un des articles de plus près, j'ai réalisé que j'avais tort et j'ai commencé à creuser. Cela ne veut pas dire que je suis juste venu à la programmation fonctionnelle, car les vrais adeptes vont y mettre leurs os et écrire en Haskell, en utilisant des monades dans la mesure du possible. Je viens de plonger et de ne l'utiliser qu'en production.

Daniil Popov: Donc, les monades sont déjà parties.

Evgeny Elchev: Déjà difficile?

Daniil Popov: J'ai essayé de suivre le même chemin, mais j'ai ouvert l'article, j'ai vu les mots «curry», «monade» et l'ai immédiatement fermé, pensant que je n'étais pas encore digne. Ai-je une chance?

Evgeny Elchev: Bien sûr. Vous ne le savez peut-être pas du tout.

En termes simples sur le fonctionnalisme


Daniil Popov: Donnons une définition simple à ceux qui n'ont jamais entendu parler d'un paradigme fonctionnel.

Evgeny Elchev: Chacun comprend les paradigmes à sa manière. Si nous prenons l'explication de Wikipedia, c'est l'utilisation de fonctions mathématiques, où l'ensemble du programme est interprété comme une fonction mathématique.

L'approche fonctionnelle (FP) consiste à utiliser dans votre travail des fonctions qui n'ont que des arguments d'entrée et une valeur de sortie. Si l'ensemble du programme se compose de ces fonctions, il s'agit d'un programme fonctionnel.

Daniil Popov: OOP était une suite logique de la programmation procédurale habituelle et a résolu le problème de l'encapsulation des données dans les classes. Quels problèmes la programmation fonctionnelle devrait-elle résoudre?

Evgeny Elchev: Les mathématiciens ont inventé la programmation fonctionnelle. Les gars se sont réunis et ont décidé de créer un paradigme où tout peut être prouvé. Il y a du code, il n'a pas encore été lancé, mais on va tout prouver. N'importe quel point du programme peut être calculé, en comprenant où nous arriverons lorsque nous autoriserons une action.

Cela semble abstrait, alors regardons un exemple de fonction pure. Nous écrivons une fonction somme qui prend deux arguments, lui passons 2 et 3, obtenons 5, et nous pouvons le prouver. C'est toujours vrai. Si tout notre programme comprend de telles fonctions, alors tout est prouvable.

Lors de la création des langues, les fonctions de base ont commencé à être manquées et des fonctionnalités supplémentaires sont apparues: lambdas, fonctions d'ordre supérieur, monades, monoïdes.

Le paradigme fonctionnel ne résout pas un seul problème, c'est le même désir d'écrire du bon code aussi simple que possible afin que les programmes soient stables et faciles à maintenir.

Si vous regardez attentivement, bon nombre des choses que nous utilisons dans la POO se reflètent dans une approche fonctionnelle. Il existe des classes dans l'OPP qui encapsulent un ensemble de champs. Dans FP, cela peut également être fait en utilisant des classes de types. Comme Vitaly Bragilevsky aime à dire : "Si vous regardez la tablette où les données vont le long des lignes et les colonnes de fonction, alors l'IF va le long des colonnes, OOP va le long des lignes." C’est tout.

Daniil Popov: Comment FI est-il lié à d'autres paradigmes? Puis-je écrire fonctionnellement dans la POO? Comment mélanger les paradigmes et cela a-t-il un sens?

Evgeny Elchev: Le paradigme se limite au fait que vous écrivez des fonctions avec des données. L'une des caractéristiques de la FA est l'absence d'états variables. Si vos données sont une classe, il n'y a aucun problème. Si la classe est entièrement immuable, elle peut être utilisée. Une classe est simplement un type, comme une chaîne ou un nombre, mais plus complexe, composé de plusieurs valeurs.

Daniil Popov: Vous avez dit plus tôt que vous pouvez prouver l'exactitude mathématique d'un programme si vous l'écrivez exclusivement de manière fonctionnelle. Ensuite, la blague «compilé - fonctionne» pour les langages fonctionnels cesse d'être une blague, non?

Evgeny Elchev: Si vous regardez les erreurs d'E / S, alors oui. Auparavant, les programmeurs se débattaient avec le problème: connecté au réseau, pas de réseau, retourné nul, et tout est tombé. Pour la solution, le moyen le plus simple était de vérifier ce qui est arrivé - nul / non nul, mais comme il y avait un risque que tout ne soit pas pris en compte, le programme pouvait compiler et planter.

Dans les langues modernes, c'est décidé. Dans Haskell, vous pouvez écrire un programme qui fonctionnera et ne plantera pas, mais personne ne dira à quel point cela fonctionne correctement. Bien sûr, il existe des types stricts, et vous ne pouvez pas faire d'erreur en ajoutant un numéro à une chaîne, mais vous pouvez toujours laisser des bogues dans l'application, et cela fonctionnera.

Lieu d'approche fonctionnelle à Swift


Alexei Kudryavtsev: Dans quelle mesure Swift peut-il être appelé un langage fonctionnel?

Evgeny Elchev: C'est possible. La fonctionnalité est positionnée comme sans état, mais vous pouvez écrire en Swift en évitant de tels états. Dans le même temps, Swift n'est pas la même chose que l'écriture sous iOS, où il y a des états partout. Bien sûr, dans Swift, il n'y a pas d'instructions spéciales comme dans Haskell, où toutes les fonctions sont propres par défaut et le compilateur ne vous permettra pas d'accéder à l'état et de le modifier. Si vous marquez la fonction comme "sale", les modifications deviennent disponibles.

Alexei Kudryavtsev: Dans le deuxième ou le troisième Swift, il y avait un modificateur pur, mais il n'agissait qu'au niveau de la compilation afin que les valeurs globales ne changent pas. Vous y avez écrit quelque chose, mais le compilateur a tout coupé.

Evgeny Elchev: Oui, dans iOS, le compilateur ne suivra pas cela. Tout est entièrement sur notre conscience: comme vous l'écrivez, il en sera ainsi.

Alexei Kudryavtsev: Vous dites qu'il y a beaucoup d'états dans les applications iOS, mais où et quoi en faire si vous écrivez dans un style fonctionnel?

Evgeny Elchev: L'état le plus important est l'interface utilisateur, par exemple, les champs de saisie. On ne peut pratiquement rien faire avec eux. Vous pouvez essayer d'en faire un résumé, de les rassembler en un seul endroit et d'écrire autant de code que possible sans les prendre en compte. Par exemple, vous écrivez une fonction sale qui obtient toutes les données de l'interface utilisateur.

Dans mon article, j'ai donné un exemple de formulaire d'autorisation, où il est important que l'utilisateur entre un nom d'utilisateur / mot de passe. Nous écrivons une fonction sale qui renvoie une structure avec des données d'autorisation, puis nous écrivons du code propre dessus. Nous avons obtenu ces données, validées, si le résultat est valide, envoyez une demande au serveur. Une demande de serveur est également une fonction sale, et le traiter entièrement peut être propre. «Reçu, analysé» est une fonction linéaire: l'entrée des données, la sortie est notre structure. Ils ont ensuite été transformés, filtrés et peuvent à nouveau être affichés à l'écran.

Alexei Kudryavtsev : Dans Haskell, le compilateur aide beaucoup. Si l'état vient de quelque part, toute la chaîne d'appel sera considérée comme sale et vous devez tout emballer dans des monades. Si la fonction est pure, la mise en cache des résultats fonctionne - la même sortie est toujours la même sortie. Dans Swift, vous devez implémenter les cartes vous-même et essayer de renvoyer le résultat s'il est déjà mis en cache.

Daniil Popov: La plupart des langues modernes sont considérées comme multi-paradigmatiques et beaucoup ont des caractéristiques fonctionnelles. Par exemple, en Java, il existe une annotation spéciale pour l'interface - @FunctionalInterface , qui oblige le développeur à définir une seule méthode dans l'interface, de sorte que cette interface sous la forme de lambdas soit ensuite utilisée dans tout le code. Lorsque vous ajoutez une deuxième méthode ou supprimez une méthode existante, le compilateur commencera à jurer qu'elle a cessé d'être une interface fonctionnelle. Swift, en dehors de la plate-forme iOS, possède-t-il de telles fonctionnalités?

Evgeny Elchev: J'ai du mal à comprendre ce que fait une telle annotation en Java. Si vous voulez dire que vous implémentez cette interface à la classe, puis que vous implémentez une seule méthode, il n'y a pas de telles restrictions dans Swift. Vous pouvez créer des typealias, les nommer et les utiliser comme type de fonction comme type d'argument, type de variable afin d'assigner une fermeture. Vous pouvez définir des contraintes - arguments de fermeture d'entrée et de sortie. Les fonctions d'ordre supérieur elles-mêmes qui peuvent prendre des fermetures sont des polymorphismes, et dans Swift vous pouvez créer des polymorphismes sur des types, non limités aux objets.

Mais je ne connais pas de choses fonctionnelles spécifiques. Il y avait du curry dans le premier Swift, mais il a été coupé. Maintenant, nous pouvons écrire une fonction pour nous curry, ou écrire une fonction pour qu'elle renvoie des fermetures les unes dans les autres, mais ce n'est pas tout à fait correct.

Nous n'avons ni foncteurs ni monades en boîte. Ils ne peuvent même pas être écrits. Les nouvelles fonctionnalités de Swift 5.1 devraient aider à le faire, mais j'ai essayé d'écrire un tel code, et xCode est tombé.

En principe, dans Swift, si vous le souhaitez, il est facile de tout faire vous-même. Il y a déjà une monade optionnelle prête à l'emploi (à Haskell - peut-être). Elle a une carte et un plan pour construire un calcul linéaire.

Swift a une puissante correspondance de motifs. Switch, qui existe dans presque toutes les langues et associe dans la plupart des cas un entier à une unité, peut associer une variable à un modèle, des plages, des types spécifiques, extraire des valeurs de types liés. Il y a carthage - vous composez un nouveau type, en en passant plusieurs autres. Sur cette base, vous pouvez également faire une correspondance de modèle. Il existe une énumération qui peut limiter les types, leur lier des types connexes.

Alexei Kudryavtsev: Je préciserai que les types apparentés sont similaires aux classes scellées Kotlin. Il s'agit de l'énumération à l'intérieur du boîtier dans laquelle vous pouvez placer la valeur liée. En switch, vous pouvez écrire: voici le cas, développer, à l'intérieur de l'objet. Par exemple, les cas d'utilisateurs et d'entreprises avec des objets correspondants peuvent être énumérés et commutés. Seules les classes scellées sont extensibles et le commutateur est fini.

Pourquoi un mobilisateur a-t-il besoin du fonctionnalisme?


Daniil Popov: En quoi une approche fonctionnelle est-elle utile pour le développement mobile? Y a-t-il des problèmes qu'il résout?

Evgeny Elchev: Il n'y a pas de problème spécifique qui puisse être résolu précisément à l'aide d'une programmation fonctionnelle.

La chose la plus importante est que, suivant ces principes, même si cela ne fonctionne pas, nous devons abandonner les conditions, car ce sont les principales douleurs.

En les abandonnant, vous rendez votre code plus compréhensible. Je ne dis pas qu'il y aura moins d'erreurs, car cela doit être au moins mesuré. Cependant, lorsque vous commencez à implémenter quelque chose, le code change. Il arrive souvent que vous regardiez le code et tout ce qu'il contient, mais vous commencez à réécrire, à permuter, à supprimer les éléments inutiles et plus faciles à lire.

En suivant le paradigme fonctionnel, vous obtenez une source d'inspiration supplémentaire.

Daniil Popov: Si je commence à écrire de telles classes immuables dans le langage OOP et à utiliser des méthodes immuables, puis-je dire que j'écris fonctionnellement?

Evgeny Elchev: Oui, pendant que vous commencez à voir les pros. Il devient plus facile de tester des méthodes en raison de l'absence d'un état global, il est plus facile de composer une chaîne de calculs à partir de méthodes.

Daniil Popov: Dans votre article, vous expliquez ce qu'est une fonction pure et des effets secondaires. Vous donnez un exemple avec sommation, où la fonction modifie également l'état externe. Le problème est que lorsque vous lisez un tel code, il est difficile de garder à l'esprit tous les changements: vous devez regarder cette variable globale, qui d'autre y lit, qui d'autre lui écrit ce qui peut arriver. Mais l'approche fonctionnelle vous permet de rester dans le flux, de ne pas aller dans les classes voisines, il vous suffit de lire le code.

Alexei Kudryavtsev: Si vous êtes dans un langage fonctionnel, alors d'une part, il vous est plus facile d'écrire du code, mais d'autre part, vous devez comprendre dans quel type de monade vous vous trouvez actuellement.

Evgeny Elchev: Oui, mais lorsque vous commencez à tout écrire sur des fonctions pures, d'autres problèmes surviennent. Par exemple, comment construire une longue chaîne de calculs. Dans le style habituel, sans y penser, vous videz facilement les données qui n'étaient pas là au départ. Dans une approche fonctionnelle, cela ne peut pas être fait: vous devez briser les chaînes, connecter tous les calculs utilisés dans plusieurs méthodes aux états. Vous devez vous y habituer.

D'un autre côté, contrairement aux classes dans OPP, qui rendent le code ossifié et difficile à composer, les fonctions peuvent être plus flexibles. Vous pouvez écrire une fonction, ajouter de la liberté à l'aide de la fermeture, lancer ces fonctions et les combiner en chaînes.

Alexei Kudryavtsev: C'est similaire à l'idéologie Unix: il y a bash, terminal et vous pouvez transférer des données de petits programmes qui font une petite action à d'autres.

Daniil Popov: Cela m'a rappelé l'approche Rx, où ils écrivent des chaînes géantes.

Evgeny Elchev: Vous avez tous les deux raison. Et Unix-way est à ce sujet, et Rx est une fusion de l'idée de liaison et de réactivité. Dans FP, nous nous lions à la source de l'événement et dans la chaîne de calcul nous le modifions, en liant le résultat à l'état final.

Daniil Popov: Les langues multi-paradigmes sont-elles bonnes du tout, est-ce pratique et utile que la langue puisse faire ceci et cela?

Evgeny Elchev: Si vous suivez strictement une sorte de paradigme, il y aura toujours des choses gênantes à faire. Il y a des choses qui sont difficiles à réaliser dans un style fonctionnel, par exemple, le stockage de l'état et la création d'un cache.

Quand il est possible de choisir un outil qui convient mieux à une tâche spécifique - c'est cool.

Vous pouvez créer une classe, y créer plusieurs méthodes dans un style fonctionnel et organiser le code de manière concise en chaînes, ou abandonner complètement la classe, créer les fonctions nécessaires et les utiliser.

L'inconvénient est qu'il y a un dilemme de choix et plus il y a d'options, plus il est difficile de choisir. Il devient également plus difficile à comprendre: plus il y a d'options, plus il est difficile de lire le code.

À propos de Monad Jam


Alexei Kudryavtsev: Revenons au fonctionnalisme, qu'est-ce qu'une monade?

Evgeny Elchev: Je l'appellerais un conteneur dans lequel vous pouvez combiner les chaînes de calculs. Le moyen le plus simple est un conteneur auquel vous pouvez appliquer la fonction et la convertir en un nouveau conteneur avec une valeur modifiée.

Imaginez la boîte dans laquelle se trouve la fraise, et il y a un appareil qui vous permet de faire de la confiture de fraises, mais vous ne pouvez pas y mettre une boîte de fraises, vous devez la verser. Monades - c'est la chose même qui vous permet de mettre une boîte dans l'appareil.

Ce n'est pas un état au sens direct, car l'état est stocké séparément, mais voici le contexte (case) avec la valeur et vous passez de l'un à l'autre. Il s'agit du transfert d'informations d'un calcul à un autre.

Alexei Kudryavtsev: Il s'avère que dans une approche fonctionnelle, pour faire de la confiture, il faut entrer dans la boîte ...

Evgeny Elchev: La beauté est que vous n'avez pas à monter dans la boîte. Vous pouvez jeter une boîte.

Fonctionnalité pour l'élite?


Daniil Popov: Il existe une opinion selon laquelle la programmation fonctionnelle ne peut pas être pratiquée sans un doctorat en mathématiques. Est-ce vrai?

Evgeny Elchev: Ce n'est pas vrai. La connaissance des mathématiques, bien sûr, améliore tout, mais j'ai oublié les mathématiques après l'obtention du diplôme et je vis normalement. En fait, tous ces outils sont incorporés dans les langues pour résoudre des problèmes spécifiques. Ils peuvent être utilisés sans essayer de prouver mathématiquement. Pendant que vous compilez une équation d'un point de vue mathématique, il sera plus rapide et plus facile de lancer quelques lignes de code en tapant, et elles fonctionneront.

Alexei Kudryavtsev: Dans quelle mesure un passe-temps pour une approche fonctionnelle peut-il interférer avec le développement de produits? Si une partie du code est déjà écrit fonctionnellement, y a-t-il des difficultés à travailler avec lui?

Evgeny Elchev: Pas du tout. Si vous n'êtes pas un maniaque et que vous n'écrirez pas un immense écosystème avec des décorateurs, vous pouvez utiliser la même correspondance de motifs.

Ce sera plus difficile si vous voulez passer à un nouvel élément de fonctionnalisme. Par exemple, le cinquième Swift et la monade de résultat sont récemment apparus, vous ne l'aviez pas utilisé auparavant, mais maintenant vous avez décidé que tout y serait. Vous prenez la fonction de requête sur le réseau et écrivez que son résultat est maintenant le résultat (données ou erreur), et vous décidez de combiner avec la requête suivante, et là vous avez une fermeture séparée avec la valeur et l'erreur, et vous devez la réécrire. J'ai commencé à écrire comme ça en un seul endroit, je me suis réveillé deux jours plus tard, quand j'ai réécrit la moitié du code, j'ai également fait de nouveaux wrappers pour que les bibliothèques s'intègrent magnifiquement.

Par où commencer?


Daniil Popov: Que doit lire un débutant pour comprendre la programmation fonctionnelle?

Evgeny Elchev: Nous devons prendre un langage purement fonctionnel, par exemple, Haskell et l'essayer dans la pratique. Vous prenez un manuel et faites les exemples les plus simples. Ici, vous comprenez l'approche - lorsqu'il n'y a pas de for, vous ne pouvez pas créer une variable dans laquelle vous pouvez modifier la valeur. Personnellement, j'ai pris une fois le livre "Learn Haskell in the name of good", où tout est décrit dans un langage simple. Après cela, vous pouvez commencer à lire des articles sur Internet: sur l'apparence des monades dans Swift, sur les types de données algébriques. Quelques articles, et il devient clair que cela ne devrait pas avoir peur.

Daniil Popov : La chose la plus difficile est de briser le paradigme dans votre propre tête.

Evgeny Elchev: Pas besoin de plonger brusquement dans la programmation fonctionnelle. Beaucoup de gens pensent qu'ils vont tous les deux s'asseoir et commencer à écrire fonctionnellement - c'est faux.

Alexei Kudryavtsev: La chose la plus cool que j'ai vue était un cours sur Stepic par Haskell de Denis Moskvin . Vous commencez par additionner quelques nombres, et vous terminez en enveloppant les monades dans des monades. Et si vous voulez vous briser complètement l'esprit, c'est-à-dire que le livre «La structure de l'interprétation des programmes informatiques» est un cours en lisp à partir d'exemples simples jusqu'à ce que vous écrivez un interprète lisp en lisp.

Si la peur principale du fonctionnalisme est passée, jetez un œil au rapport de Vitaliy Bragilevsky du printemps AppsConf. Cependant, à l'automne de AppsConf, nous aborderons des sujets non moins intéressants - la communauté iOS attend avec impatience un rapport de Daniil Goncharov sur la rétro-ingénierie Bluetooth , et les développeurs Android et Alexander Smirnov discuteront des approches actuelles pour créer des animations.

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


All Articles