Question

Considérons un type de données avec de nombreux constructeurs:

data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int

Je veux écrire une fonction pour vérifier si deux valeurs sont produits avec le même constructeur:

sameK (Alpha _) (Alpha _) = True
sameK (Beta _) (Beta _) = True
sameK (Gamma _ _) (Gamma _ _) = True
sameK _ _ = False

Le maintien sameK est pas très amusant, il ne peut pas être vérifié pour l'exactitude facilement. Par exemple, lorsque de nouveaux constructeurs sont ajoutés à T, il est facile d'oublier de mettre à jour sameK. J'omis une ligne pour donner un exemple:

-- it’s easy to forget:
-- sameK (Delta _) (Delta _) = True

La question est de savoir comment éviter boilerplate dans sameK? Ou comment assurez-vous qu'il vérifie pour tous les constructeurs de T?


La solution que j'ai trouvé est d'utiliser des types de données distincts pour chacun des constructeurs, dérivant Data.Typeable et déclarant une classe de type commun, mais je ne aime pas cette solution, car il est beaucoup moins lisible et par ailleurs juste un type simple algébrique fonctionne pour moi:

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Typeable

class Tlike t where
  value :: t -> t
  value = id

data Alpha = Alpha Int deriving Typeable
data Beta = Beta Int deriving Typeable
data Gamma = Gamma Int Int deriving Typeable
data Delta = Delta Int deriving Typeable

instance Tlike Alpha
instance Tlike Beta
instance Tlike Gamma
instance Tlike Delta

sameK :: (Tlike t, Typeable t, Tlike t', Typeable t') => t -> t' -> Bool
sameK a b = typeOf a == typeOf b
Était-ce utile?

La solution

Regardez le module Data.Data, la fonction toConstr en particulier. Avec {-# LANGUAGE DeriveDataTypeable #-} que vous obtiendrez une solution à 1 ligne qui fonctionne pour tout type qui est une instance de Data.Data. Vous n'avez pas besoin de comprendre tous SYB!

Si, pour une raison quelconque (coincé avec étreintes?), Qui ne sont pas une option, alors voici un hack très laid et très lent. Il ne fonctionne que si votre type de données est Showable. (Par exemple en utilisant deriving (Show) - ce qui signifie aucun type de fonction à l'intérieur, par exemple)

constrT :: T -> String
constrT = head . words . show
sameK x y = constrT x == constrT y

constrT obtient la représentation de chaîne du constructeur le plus externe d'une valeur de T en le montrant, hacher en mots puis obtenir le premier. Je donne une signature de type explicite de sorte que vous n'êtes pas tenté de l'utiliser sur d'autres types (et de se soustraire à la restriction de monomorphisme).

Quelques inconvénients notables:

  • Ce pauses horriblement quand le type a des constructeurs infixes (tels que data T2 = Eta Int | T2 :^: T2)
  • Si certains de vos constructeurs ont un préfixe commun, cela va devenir plus lent, comme une plus grande partie des chaînes doit être comparé.
  • ne fonctionne pas sur les types avec un show personnalisé, comme de nombreux types de bibliothèques.

Cela dit, il Haskell 98 ... mais qui est sur la seule bonne chose que je peux dire à ce sujet!

Autres conseils

Une autre façon possible:

sameK x y = f x == f y
  where f (Alpha _)   = 0
        f (Beta _)    = 1
        f (Gamma _ _) = 2
        -- runtime error when Delta value encountered

Une erreur d'exécution est pas idéal, mais mieux que de donner en silence la mauvaise réponse.

Vous aurez besoin d'utiliser une bibliothèque de médicaments génériques comme la ferraille Votre boilerplate ou uniplaque à faire en général.

Si vous ne voulez pas être si lourd remis, vous pouvez utiliser la solution de Dave Hinton, en même temps que le raccourci enregistrement vide:

...
where f (Alpha {}) = 0
      f (Beta {}) = 1
      f (Gamma {}) = 2

Vous ne devez pas savoir combien chaque constructeur a args. Mais il laisse évidemment encore à désirer.

Dans certains cas, "Scrap Votre boilerplate" bibliothèque aide.

http://www.haskell.org/haskellwiki/Scrap_your_boilerplate

Vous pouvez certainement utiliser les médicaments génériques pour éliminer le passe-partout. Votre code est un exemple de manuel pourquoi je (et beaucoup d'autres jamais utiliser le caractère générique _ au niveau supérieur). Alors qu'il est fastidieux d'écrire tous les cas, il est moins pénible que de traiter les bugs.

Dans cet exemple, je serais heureux d'utiliser non seulement la solution de Dave Hinton mais je gifler un pragma INLINE sur la fonction auxiliaire f.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top