2010-12-12 39 views
12

lecture « monde réel Haskell » J'ai trouvé une question intresting sur les types de données:utilisation des types de données Haskell bonnes practicies

Cette correspondance de motif et l'accès de position de données font ressembler à vous couplage très serré entre les données et code qui fonctionne dessus (essayez d'ajouter quelque chose à Book, ou pire, changez le type d'une pièce existante).

Cela est généralement une très mauvaise chose à impératif (en particulier OO) langues ... est-il pas considéré comme un problème dans Haskell? source at RWH comments

Et vraiment, écrire des programmes Haskell je trouve que quand je fais petit changement à la structure de type de données affecte presque toutes les fonctions qui utilisent ce type de données. Il existe peut-être quelques bonnes pratiques pour l'utilisation du type de données. Comment puis-je minimiser le couplage de code?

Répondre

13

Il ya un compromis certain à faire, le code haskell en général, et les types de données algébriques en particulier, a tendance à tomber dans le dur de changer le type, mais facile à ajouter des fonctions sur le type. Cela optimise (à l'avance) les types de données complets et bien conçus.

Tout cela dit, il y a un certain nombre de choses que vous pouvez faire pour réduire le couplage.

  • définirons des fonctions de bonne bibliothèque, en définissant un ensemble complet de fonctions et combinateurs d'ordre supérieur qui sont utiles pour interagir avec vos type de données que vous réduirez le couplage.On dit souvent que lorsque vous pensez à la correspondance de modèles, il existe une meilleure solution utilisant des fonctions d'ordre supérieur. Si vous cherchez ces situations, vous serez dans un meilleur endroit.

  • Exposez vos structures de données sous forme de types plus abstraits. Cela signifie implémenter toutes les classes de types appropriées. Cela vous aidera à définir les fonctions d'une bibliothèque car vous obtiendrez un paquet gratuit avec l'une des classes de types que vous implémentez, par exemple regardez les opérations sur Functor ou Monad.

  • Masquer (autant que possible) tous les constructeurs de type. Les constructeurs exposent les détails de l'implémentation et encouragent le couplage. Astuce: ceci est lié à la définition d'une bonne API pour interagir avec votre type, les consommateurs de votre type devraient rarement, voire jamais, utiliser les constructeurs de type.

La communauté haskell semble particulièrement bien à cela, si vous regardez la plupart des bibliothèques sur hackage, vous trouverez de très bons exemples de mise en œuvre des classes de type et d'exposer les fonctions de bonne bibliothèque.

+0

Je ne pense pas que vous puissiez vous débrouiller sans correspondance. * La récursivité explicite * est cependant rarement nécessaire. – delnan

+0

+1 pour donner un nom à la bête. – fuz

7

D'abord, je voudrais mentionner que, à mon avis, il y a deux types d'accouplements:

  • Celui qui rend votre cessez de code à compiler lorsque vous modifiez un et oublier de changer l'autre

  • celui qui fait lorsque vous changez votre buggy de code et un oubliez de changer l'autre

Bien que les deux sont problématiques, le premier est beaucoup moins de ah eadache, et cela semble être celui dont vous parlez.

Je pense que le principal problème que vous évoquez est dû à la sur-utilisation des arguments positionnels. Haskell vous oblige presque à avoir des arguments positionnels dans vos fonctions ordinaires, mais vous pouvez les éviter dans vos produits de type (enregistrements). Il suffit d'utiliser les enregistrements au lieu de plusieurs champs anonymes dans les constructeurs de données, et ensuite vous pouvez faire correspondre les champs que vous voulez, par nom.

bad (Blah _ y) = ...

good (Blah{y = y}) = ...

Évitez de trop en utilisant tuples, en particulier ceux au-delà de 2 triplets, et créer librement des documents/newtypes autour des choses pour éviter le sens de position. Ce que vous décrivez est communément appelé le problème d'expression - http://en.wikipedia.org/wiki/Expression_Problem

+1

Merci pour votre réponse. Je ne suis pas très familier avec la syntaxe d'enregistrement car le nom de champ devient global dans le module. Peut-être qu'il y a un remède? – aindl

+0

IMO the cure a de très petits modules :-) – Peaker

+0

@masterzim Le problème de pollution de l'espace de noms avec les enregistrements recevra un correctif dans une prochaine version de GHC, qui vous permettra de donner plusieurs types aux mêmes noms d'enregistrements, et l'accesseur fonctionne obtenir gratuitement sera général sur les types qui ont ce champ d'enregistrement. – kqr

10

En plus de ce qui a été dit:

Une approche intéressante est le style « scrap your boilerplate » de définir des fonctions sur les types de données, qui utilise des fonctions génériques (par opposition à la correspondance de modèle explicite) de définir des fonctions sur la constructeurs d'un type de données. En regardant les papiers "scrap your boilerplate", vous verrez des exemples de fonctions qui peuvent faire face à des changements dans la structure d'un type de données.

Une deuxième méthode, comme Hibberd a fait remarquer, est d'utiliser plis, Cartes, se déploie, et d'autres combinateurs récursivité, pour définir vos fonctions. Lorsque vous écrivez des fonctions en utilisant des fonctions d'ordre supérieur, il est souvent possible de traiter de petites modifications du type de données sous-jacent dans les déclarations d'instance de Functor, Foldable, etc.