Bonnes normes de codage Haskell

Quelqu’un pourrait-il fournir un lien vers une bonne norme de codage pour Haskell? J’ai trouvé ceci et cela , mais ils sont loin d’être complets. Sans oublier que le HaskellWiki inclut de telles “gemmes” comme “classes d’utilisation avec soin” et que “la définition des identifiants d’infixes symboliques devrait être laissée aux seuls auteurs de bibliothèques”.

Question vraiment difficile. J’espère que vos réponses donneront quelque chose de bien. En attendant, voici un catalogue d’erreurs ou d’autres choses ennuyeuses que j’ai trouvées dans le code des débutants. Il y a un certain chevauchement avec la page de style Cal Tech sur laquelle Kornel Kisielewicz fait référence. Certains de mes conseils sont tout aussi vagues et inutiles que les “gemmes” d’HaskellWiki, mais j’espère au moins que c’est un meilleur conseil 🙂

  • Formatez votre code pour qu’il tienne dans 80 colonnes. (Les utilisateurs avancés peuvent préférer 87 ou 88; au-delà, cela pousse.)

  • N’oubliez pas que let clauses et where créent un nid de définitions mutuellement récursif, pas une séquence de définitions.

  • Tirez parti des clauses where , en particulier de leur capacité à voir les parameters de fonction qui sont déjà dans la scope (conseils vagues). Si vous critiquez vraiment Haskell, votre code devrait contenir beaucoup plus de liens que de -bindings. Trop de let -bindings est le signe d’un programmeur ML ou d’un programmeur Lisp non reconstruit.

  • Évitez les parenthèses redondantes. Certains endroits où les parenthèses redondantes sont particulièrement choquantes sont

    • Autour de la condition dans une expression if (vous désigne comme un programmeur C non reconstruit)

    • Autour d’une application qui est elle-même l’argument d’un opérateur infixé ( l’application Function se lie plus étroitement que tout opérateur infixé . Ce fait devrait être gravé dans le cerveau de chaque Haskeller, de la même manière brûlé.)

  • Placez des espaces autour des opérateurs infixes. Mettez un espace après chaque virgule dans un littéral tuple.

  • Préférez un espace entre une fonction et son argument, même si l’argument est entre parenthèses.

  • Utilisez l’opérateur $ judicieusement pour réduire les parenthèses. Soyez conscient de la relation étroite entre $ et infixe . :

     f $ g $ hx == (f . g . h) x == f . g . h $ x 
  • Ne négligez pas les types intégrés et l’un Either .

  • N’écrivez jamais if then True else False ; la phrase correcte est simplement .

  • N’utilisez pas la head ou la tail lorsque vous pouvez utiliser la correspondance de motif.

  • Ne négligez pas la composition des fonctions avec l’opérateur de point infixé.

  • Utilisez les sauts de ligne avec soin. Les sauts de ligne peuvent améliorer la lisibilité, mais il existe un compromis: votre éditeur ne peut afficher que 40 à 50 lignes à la fois. Si vous avez besoin de lire et de comprendre une grande fonction en même temps, vous ne devez pas abuser des sauts de ligne.

  • Préférez presque toujours les commentaires qui se terminent à la fin des commentaires {- ... -} . Les commentaires contre-indiqués peuvent être appropriés pour les gros en-têtes – c’est tout.

  • Atsortingbuez à chaque fonction de niveau supérieur une signature de type explicite.

  • Si possible, alignez les lignes, = signes, et même les parenthèses et les virgules qui apparaissent dans les lignes adjacentes.

  • Influencé par le GHC central, je préfère très peu utiliser camelCase pour les identifiants exportés et short_name avec des short_name de soulignement pour les variables locales where connecter ou let .

Quelques bonnes règles à suivre:

  • Consultez HLint pour vous assurer que vous n’avez pas d’accolades superflues et que votre code n’est pas inutile.
  • Évitez de recréer des fonctions de bibliothèque existantes. Hoogle peut vous aider à les trouver.
    • Souvent, les fonctions de bibliothèque existantes sont plus générales que ce que l’on allait faire. Par exemple, si vous voulez, Maybe (Maybe a) -> Maybe a , alors join -le, entre autres choses.
  • La dénomination des arguments et la documentation sont parfois importantes.
    • Pour une fonction comme replicate :: Int -> a -> [a] , il est assez évident de savoir ce que chacun des arguments fait, à partir de leur seul type.
    • Pour une fonction qui prend plusieurs arguments du même type, comme isPrefixOf :: (Eq a) => [a] -> [a] -> Bool , nommer / documenter les arguments est plus important.
  • Si une fonction existe uniquement pour servir une autre fonction, et n’est pas utile par ailleurs, et / ou qu’il est difficile de la nommer, alors elle devrait probablement exister dans la clause where l’appelant plutôt que dans la scope du module.
  • SEC
    • Utilisez Template-Haskell si nécessaire.
    • Les paquets de fonctions comme zip3 , zipWith3 , zip4 , zipWith4 , etc. Utilisez plutôt le style Applicative avec ZipList s. Vous n’avez probablement jamais besoin de fonctions comme celles-là.
    • Dériver les instances automatiquement Le package derive peut vous aider à dériver des instances pour des classes de type telles que Functor (il n’y a qu’une seule façon correcte de faire d’un type une instance de Functor ).
  • Code plus général présente plusieurs avantages:
    • C’est plus utile et réutilisable.
    • Il est moins sujet aux bogues car il y a plus de contraintes.
      • Par exemple, si vous voulez programmer concat :: [[a]] -> [a] , notez comment cela peut être plus général que join :: Monad m => m (ma) -> ma . Il y a moins de place pour les erreurs lors de la programmation de la join car lorsque vous programmez un concat vous pouvez inverser les listes par erreur et, en les join , vous ne pouvez rien faire.
  • Lorsque vous utilisez la même stack de transformateurs monad à plusieurs endroits dans votre code, créez un synonyme de type. Cela rendra les types plus courts, plus concis et plus faciles à modifier en bloc.
  • Méfiez-vous de “paresseux IO”. Par exemple, readFile ne readFile pas vraiment le contenu du fichier au moment de la lecture du fichier.
  • Évitez d’indenter autant que je ne trouve pas le code.
  • Si votre type est logiquement une instance d’une classe de type, faites-en une instance.
    • L’instance peut remplacer d’autres fonctions d’interface que vous avez pu envisager avec des fonctions familières.
    • Remarque: S’il existe plusieurs instances logiques, créez des wrappers newtype pour les instances.
    • Rendre les différentes instances cohérentes. Il aurait été très déroutant / mauvais si la liste Applicative se comportait comme ZipList .

Je suggère de jeter un oeil à ce vérificateur de style .

  • J’aime essayer d’organiser les fonctions autant que possible en compositions de style sans points en faisant des choses comme:

     func = boo . boppity . bippity . snd where boo = ... boppity = ... bippity = ... 
  • J’aime utiliser ($) uniquement pour éviter les parens nesteds ou les expressions entre parenthèses longues

  • … je pensais avoir un peu plus en moi, eh bien

J’ai trouvé un bon fichier de démarques couvrant presque tous les aspects du style de code de haskell. Il peut être utilisé comme aide-mémoire. Vous pouvez le trouver ici: lien