web-dev-qa-db-fra.com

Vous utilisez un système de type «fort» dans le monde réel, par exemple, pour des applications Web à grande échelle?

Je sais que c'est une question très large, ambiguë et peut-être philosophique. Dans une certaine mesure, le mot-clé le plus important de la question - système de type "fort" - lui-même, est mal défini . Alors, laissez-moi essayer d'expliquer ce que je veux dire.

Contexte général de la question

Nous avons construit une application web à très grande échelle en Ruby on Rails et nous sommes généralement satisfaits de notre pile. Quand nous voulons, nous pouvons expédier des trucs très rapidement - quelque chose qui fonctionne pour 90% du cas "commercial", sans trop se soucier des cas Edge de 10%. D'un autre côté, avec l'aide des révisions de code et de la couverture des tests, nous pouvons être lent et délibéré et assurez-vous que nous couvrons toutes les bases - encore une fois, uniquement dans les situations qui méritent un examen et une sécurité plus étroits.

Cependant, au fur et à mesure que l'équipe grandit, j'ai commencé à me sentir mal à l'aise avec l'absence d'un "filet de sécurité" cuit dans notre pile.

Nous avons récemment commencé à faire du développement natif Android sur Java. Et j'ai été (agréablement) rappelé la sécurité offerte par un langage compilé/statique/fortement typé.

  • Des variables mal orthographiées, des types de données incorrects, des invocations de fonctions incorrectes et un hôte d'erreurs triviales sont détectés par votre IDE lui-même. Tout cela parce que le IDE peut s'accrocher) dans le compilateur et vérifier certains aspects de la "correction" du programme.
  • Besoin de changer une signature de fonction? Facile. Le compilateur + IDE peut vous aider à repérer TOUS les sites d'appel.
  • Besoin de vous assurer que certaines exceptions sont toujours gérées? Vérifié les exceptions à votre secours.

Maintenant, bien que ces caractéristiques de sécurité aient leurs avantages, je connais bien leurs inconvénients. Plus encore, dans le monde du Java "lourd". Par conséquent, au lieu de Java, j'ai commencé à regarder le flot de langages modernes "fortement typés" sur lesquels les gens ont commencé à travailler ces jours-ci. Par exemple: Scala, Rust, Haskell, etc. Ce qui m'intéresse le plus, c'est la puissance de leurs systèmes de type et les contrôles statiques/de compilation.

Maintenant, la question

Comment utiliser ces systèmes de type puissants et ces fonctions statiques/de compilation dans des applications plus grandes?

Par exemple, comment pourrais-je aller au-delà du type standard "bonjour monde" des introductions à ces fonctionnalités puissantes? Celui qui utilise un système de type riche pour modéliser un problème de domaine commercial? Le système de saisie aide-t-il ou gêne-t-il lorsque vous êtes dans la zone 30 000 LOC +? Qu'advient-il du filet de sécurité fourni par ces systèmes de type (et des contrôles au moment de la compilation) lorsque votre système interagit avec le monde extérieur faiblement typé, par exemple. via des API JSON ou XML, divers magasins de données, entrée utilisateur, etc.

29
Saurabh Nanda

Je vais donner une réponse courte en raison du manque de temps pour le moment, mais je travaille actuellement sur deux gros projets (> 100 000 LOC à Haskell) - flowbox.io et luna-lang.org . Nous utilisons Haskell pour toutes les parties, y compris le backend, le compilateur de notre langage de programmation et même l'interface graphique WebGL. Je dois admettre que le système de type fort et la machinerie semblable au "type dépendant" peuvent vous guider et vous épargner le fardeau et les tracas connus d'autres langues. Nous utilisons les types très largement et tout ce qui pourrait être vérifié lors de la compilation, le fait. En fait, au cours des 3 dernières années de développement, nous n'avons jamais rencontré d'erreur d'exécution ou de débordement de pile (et c'est quelque chose de vraiment incroyable). Les seules erreurs sont des erreurs logiques évidentes faites par les programmeurs. Beaucoup de gens disent que si quelque chose se compile dans Haskell, cela fonctionne et vous devez être sûr que cela ne vous soufflera pas un jour. Cela est vrai dans la plupart des situations et lorsque vous connaissez bien la langue et que vous devez éviter (comme les méthodes de typeclass non implémentées), vous serez en sécurité et tirerez de gros profits du système de typage.

En répondant à la première partie de la question: vous pouvez en apprendre davantage sur ces puissantes fonctionnalités du système de type en lisant certains grands blogs, comme:

En fait, il y a beaucoup d'autres blogs Nice là-bas (comme planète Haskell ). Quoi qu'il en soit, la meilleure méthode pour vraiment comprendre les systèmes de types avancés est de développer une bibliothèque open source utile. Nous (chez Flowbox & New Byte Order) publions un grand nombre de bibliothèques (vous pouvez les trouver sur Hackage), donc si vous ne savez pas quoi développer, vous pouvez toujours vous impliquer dans nos projets - envoyez-moi simplement un e-mail chaque fois que vous vouloir (mail disponible sur luna-lang.org ).

34
danilo2

Eh bien, le typage faible vs fort est défini de façon assez vague. En outre, étant donné que le plus proche d'une utilisation générale du "typage fort" est de renvoyer des choses qui rendent difficile la conversion de types, ce qui ne laisse rien de plus pour décrire des systèmes de types encore plus forts. C'est comme dire que si vous pouvez transporter moins de 30 livres, vous êtes faible et que tous ceux qui peuvent soulever plus sont dans la même catégorie de "fort" - une distinction trompeuse.

Je préfère donc la définition:

  • Les systèmes mal typés utilisent des types pour vous empêcher de faire certaines choses (comme des erreurs)
  • Les systèmes fortement typés utilisent des types pour faire des choses pour vous

Qu'est-ce que je veux dire par faire des choses pour vous? Eh bien, examinons l'écriture d'une API de conversion d'image dans le framework Servant (dans Haskell, mais vous n'avez pas vraiment besoin de le savoir pour suivre, vous verrez ...)

{-# LANGUAGE
    TypeOperators,
    DataKinds
    #-}

import Codec.Picture
import Data.Proxy
import Network.Wai.Handler.Warp (run)
import Servant
import Servant.JuicyPixels

main :: IO ()
main = run 8001 conversion

Cela signifie que nous voulons certains modules, y compris le package Servant et le plug-in JuicyPixels pour Servant, et que le point d'entrée principal du programme est d'exécuter la fonction de `` conversion '' sur le port 8001 en tant que serveur utilisant le backend Warp. Ignorez le bit de langue.

conversion :: Application
conversion = serve (Proxy :: Proxy ConversionApi) handler

Cela signifie que la fonction de conversion est un serveur où l'API doit correspondre au type 'ConversionApi' et les demandes sont traitées par la fonction handler

type ConversionApi
     = ReqBody '[BMP, GIF, JPEG 50, PNG, TIFF, RADIANCE] DynamicImage
    :> Post '[BMP, GIF, JPEG 50, PNG, TIFF, RADIANCE] DynamicImage

Ceci spécifie le type ConvesionApi. Il dit que nous devons accepter les types de contenu entrants spécifiés par la liste '[BMP, GIF, JPEG 50, PNG, TIFF, RADIANCE], et les traiter comme une DynamicImage, et que nous devons renvoyer une DynamicImage convertie dans la même plage de contenu les types. Ne vous inquiétez pas exactement de ce que:> signifie, considérez-le comme une magie heureuse pour l'instant.

Donc, étant donné ma définition préférée, un système faiblement typé peut désormais garantir des choses comme:

  • Vous ne renvoyez pas le mauvais type de contenu sortant
  • Vous n'analysez pas la demande entrante comme un type de contenu incorrect
  • Si notre serveur était plus compliqué, cela nous empêcherait de créer des URI malformés, mais nous ne renvoyons en fait aucune page HTML contenant des liens (et le type garantit que nous ne pouvons pas!)
  • Un système de typage faible vraiment ambitieux pourrait même vérifier pour s'assurer que nous traitons de manière exhaustive tous les types de contenu entrant et sortant, permettant au type d'agir également comme un document de spécification au lieu d'une simple contrainte.

Tous les objectifs élevés, mais pas suffisamment pour être considérés comme un système fortement typé, compte tenu de la définition ci-dessus. Et maintenant, nous devons passer à la partie difficile de l'écriture de code qui suit cette spécification. Dans un système de type vraiment fort, nous écrivons:

handler = return

Et puis nous avons terminé. Voilà, il n'y a plus de code à écrire. Il s'agit d'un serveur Web entièrement opérationnel (modulo toutes les fautes de frappe que j'ai manquées). Le type a dit au compilateur tout ce dont il a besoin pour créer notre serveur Web à partir des types et des packages (modules techniquement) que nous avons définis et importés.

Alors, comment apprenez-vous à le faire à l'échelle de l'application principale? Eh bien, ce n'est vraiment pas très différent de les utiliser dans des applications à plus petite échelle. Les types absolus ne se soucient pas de la quantité de code écrit les concernant.

L'inspection des types d'exécution est quelque chose que vous voudrez probablement éviter, car cela élimine une énorme quantité d'avantages et permet aux types de rendre votre projet plus compliqué à travailler, plutôt que d'avoir des types simplifiant les choses.

En tant que tel, il s'agit principalement de s'entraîner à modéliser des choses avec des types. Les deux principales façons de modéliser des choses (ou de construire des choses en général) sont de bas en haut et de haut en bas. De haut en bas commence avec le plus haut niveau d'opérations, et lorsque vous construisez le modèle, vous avez des pièces où vous reportez la modélisation à plus tard. La modélisation ascendante signifie que vous commencez par les opérations de base, tout comme vous commencez par les fonctions de base, puis créez des modèles de plus en plus grands jusqu'à ce que vous ayez entièrement capturé le fonctionnement du projet. De bas en haut est plus concret et probablement plus rapide à construire, mais de haut en bas peut mieux informer vos modèles de niveau inférieur sur la façon dont ils doivent réellement se comporter.

Les types sont la façon dont les programmes se rapportent aux mathématiques, littéralement, donc il n'y a pas vraiment de limite supérieure sur la complexité de leur complication, ou un point où vous pouvez en finir avec eux. Pratiquement toutes les ressources en dehors des cours universitaires de niveau supérieur sont toutes consacrées à la façon dont les types fonctionnent dans une langue particulière, vous devez donc en décider également.

Du mieux que je puisse offrir, les types peuvent être stratifiés comme suit:

  • Très faiblement tapé, des choses comme JavaScript où [] + {} est défini
  • Faible type comme Python, où vous ne pouvez pas faire [] + {}, mais cela n'est vérifié que lorsque vous essayez
  • Faible type comme C ou Java, où vous ne pouvez pas faire [] + {}, mais qui est vérifié au moment de la compilation, mais vous n'avez pas les fonctionnalités de type les plus avancées
  • À cheval sur la frontière entre les types faiblement et fortement typés, comme la métaprogrammation de modèles C++ et le code Haskell plus simple où les types ne font qu'appliquer des propriétés.
  • Complètement dans Fortement typé, comme des programmes Haskell plus compliqués où les types font des choses, comme indiqué ci-dessus
  • Le très fortement typé, comme Agda ou Idris, où les types et les valeurs interagissent et peuvent se contraindre. C'est aussi fort que les systèmes de types, et la programmation est la même que l'écriture de preuves mathématiques sur ce que fait votre programme. Remarque: coder dans Agda ce n'est pas littéralement écrire des preuves mathématiques, les types sont des théories mathématiques et les fonctions avec ces types sont des exemples constructifs prouvant ces théories.

Généralement, plus vous descendez dans cette liste, plus les types peuvent faire pour vous, mais tout en bas, vous grimpez dans la stratosphère et l'air devient un peu mince - l'écosystème du package est beaucoup plus petit et vous '' Je vais devoir écrire plus de choses vous-même que d'avoir trouvé une bibliothèque appropriée. La barrière à l'entrée augmente également au fur et à mesure que vous descendez, car vous devez en fait comprendre suffisamment le système de type pour écrire des programmes à grande échelle.

17
Steven Armstrong

Je viens de commencer à travailler sur l'équipe centrale d'une grande plateforme écrite en Scala. Vous pouvez consulter les applications open source réussies, comme Scalatra, Play ou Slick pour voir comment elles gèrent certaines de vos questions plus détaillées sur les interactions avec les formats de données dynamiques.

L'un des grands avantages que nous avons trouvés du typage fort de Scala est dans la formation des utilisateurs. L'équipe de base peut prendre des décisions et appliquer ces décisions dans le système de type, donc lorsque d'autres équipes qui sont beaucoup moins familiers avec les principes de conception doivent interagir avec le système, le compilateur les corrige, et l'équipe principale ne corrige pas constamment les choses dans les demandes de tirage. C'est un avantage énorme dans un grand système.

Bien sûr, tous les principes de conception ne peuvent pas être appliqués dans un système de types, mais plus votre système de types est solide, plus vous pouvez appliquer de principes de conception dans le compilateur.

Nous pouvons également faciliter les choses pour les utilisateurs. Souvent, pour eux, ils travaillent simplement avec des collections ou des classes de cas normales, et nous le convertissons automatiquement en JSON ou quoi que ce soit selon les besoins pour le transport réseau.

Un typage fort permet également de faire des différenciations entre des éléments comme les entrées non désinfectées et désinfectées, ce qui peut contribuer à la sécurité.

Une frappe forte aide également vos tests à se concentrer davantage sur votre comportement réel , au lieu d'avoir besoin d'un tas de tests qui testent simplement vos types. Cela rend les tests beaucoup plus agréables, plus ciblés et donc plus efficaces.

Le principal inconvénient est la méconnaissance de la langue et du paradigme linguistique, et cela peut être corrigé avec le temps. À part cela, nous avons trouvé que l'effort en valait la peine.

10
Karl Bielefeldt

Bien que ce ne soit pas une réponse directe (puisque je n'ai pas encore travaillé sur +30.000 bases de code LOC dans haskell :( ..), je vous implore de vérifier https://www.fpcomplete.com/business/resources/case-studies / qui présente de nombreuses études de cas de haskell dans des contextes industriels réels.

Un autre bon article est IMVU, qui décrit leur expérience en passant à haskell - http://engineering.imvu.com/2014/03/24/what-its-like-to-use-haskell/ .

D'après son expérience personnelle dans des applications plus grandes, le système de type très vous aide beaucoup, surtout si vous essayez d'encoder autant que possible dans les types. Le vrai pouvoir est vraiment évident quand il s'agit de refactoriser les choses - ce qui signifie que la maintenance et autres deviennent une tâche beaucoup moins inquiétante.

Je vais vider quelques liens vers des ressources que je recommande, car vous posez beaucoup de questions à la fois:

Pour terminer, le traitement du monde extérieur se fait de plusieurs manières. Il existe des bibliothèques pour vous assurer que les choses de votre côté sont de type sécurisé, comme Aeson pour JSON, Esqueleto pour SQL et bien d'autres.

8
Tehnix

Ce que j'ai vu:

J'ai travaillé quelques grandes applications Web Ruby (Rails), une grande application Web Haskell et plusieurs plus petites. Avec cette expérience, je dois dire que la vie de travail sur les applications Haskell est beaucoup plus facile que dans Rails en ce qui concerne la maintenance et la courbe d'apprentissage inférieure. Je suis d'avis que ces avantages sont à la fois dus au système de type de Haskell et au style de programmation fonctionnelle. Cependant, contrairement à beaucoup, je crois que la partie "statique" du système de type n'est qu'une grande commodité dans la mesure où il y a encore des avantages à tirer lors de l'utilisation de contrats dynamiques.

Ce que je crois

Il existe un package Nice appelé Contracts Ruby qui fournit des fonctionnalités principales qui, selon moi, aident les projets Haskell à obtenir de meilleures caractéristiques de maintenance. Contrats Ruby effectue ses vérifications au moment de l'exécution, il est donc préférable lorsqu'il est associé à une convergence de test élevée, mais il fournit toujours la même documentation en ligne et la même expression d'intention et de signification que l'utilisation d'annotations de type dans des langues telles que Haskell.

Réponse à la question

Pour répondre aux questions posées ci-dessus, il existe de nombreux endroits où l'on peut se familiariser avec Haskell et d'autres langues avec des systèmes de type avancés. Cependant, et pour être parfaitement honnête, bien que ces sources de documentation soient excellentes en elles-mêmes, elles semblent toutes un peu décevantes par rapport à la pléthore de documentation et de conseils pratiques trouvés dans Ruby, Python, Java = et d'autres langages. En tout cas, Real World Haskell vieillit mais reste une bonne ressource.

Théorie des catégories

Si vous choisissez Haskell, vous rencontrerez une grande quantité de littérature traitant de la théorie des catégories. La théorie des catégories à mon humble avis est utile mais pas nécessaire. Étant donné sa prévalence dans la communauté Haskell, il est facile de confondre les avantages et les inconvénients des types avec des sentiments sur le caractère pratique de la théorie des catégories. Il est utile de se rappeler que ce sont deux choses différentes, c'est-à-dire que les implémentations guidées par la théorie des catégories peuvent être faites dans des langages typés dynamiquement aussi bien que statiques (modulo les avantages du système de types). Les systèmes de types avancés en général ne sont pas liés à la théorie des catégories et la théorie des catégories n'est pas liée aux systèmes de types.

Plus d'informations sur les types

Au fur et à mesure que vous en apprendrez plus sur la programmation avec les types et les techniques qui s'y trouvent (ce qui se produit assez rapidement parce que c'est amusant), vous voudrez exprimer plus avec le système de types. Dans ce cas, j'examinerais certaines des ressources suivantes et me joindrais à moi pour informer les fournisseurs d'outils que nous voulons que les outils de qualité industrielle avec ces fonctionnalités ne soient emballés que dans quelque chose qui expose une interface facile à utiliser (comme Contracts Ruby):

3
Eric

Tout d'abord, j'ai l'impression qu'il y a une confusion dans les réponses entre typé faiblement contre fortement typé et statique versus dynamiquement typé. Lier l'OP fourni fait clairement la distinction:

Un système de type fort est un système de type qui a une restriction au moment de la compilation ou une fonctionnalité d'exécution que vous trouvez attrayante.

Un système de type faible est un système de type qui n'a pas cette restriction ou fonctionnalité.

Par exemple, C, C++ et Java sont typés statiquement car les variables sont typées au moment de la compilation. Pourtant, C et C++ peuvent être considérés comme faiblement typés car le langage permet de contourner les restrictions en utilisant void * pointeurs et casts . Plus d'informations sur ce sujet.

Sur cette distinction, un typage fort ne peut être que meilleur. Plus l'échec est précoce, mieux c'est.

Cependant, lors de l'écriture de grands programmes, je ne pense pas que le système de type joue un rôle important. Le noyau Linux est composé de dix millions de LOC écrit en C et Assembly et est considéré comme un programme très stable, il est à des kilomètres de mes 200 Java qui sont probablement pleines de failles de sécurité. De même, bien que typées dynamiquement " les langages de script "souffrent d'une mauvaise réputation quand il s'agit d'écrire de gros programmes, il y a parfois des preuves qu'il n'est pas mérité (comme Python Django, plus de 70k LOC))

À mon avis, c'est une question de qualité. La responsabilité de l'évolutivité des grandes applications ne devrait être assumée que par les programmeurs et les architectes et leur volonté de rendre l'application propre, testée, bien documentée, etc.

2
Arthur Havlicek