Domanda

Consideriamo un tipo di dati con molti costruttori:

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

Voglio scrivere una funzione per verificare se due valori sono prodotti con lo stesso costruttore:

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

Il mantenimento sameK non è molto divertente, ma non può essere verificata la correttezza facilmente. Ad esempio, quando i nuovi costruttori si aggiungono a T, è facile dimenticare di aggiornamento sameK. Ho omesso una linea per fare un esempio:

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

La domanda è: come evitare boilerplate in sameK? O il modo di assicurarsi che i controlli per tutti i costruttori T?


La soluzione che ho trovato è quello di utilizzare i tipi di dati separati per ciascuno dei costruttori, derivanti Data.Typeable, e dichiarando una classe tipo comune, ma non mi piace questa soluzione, perché è molto meno leggibile e altrimenti solo un tipo semplice algebrico funziona per me:

{-# 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
È stato utile?

Soluzione

Guarda il modulo Data.Data, la funzione toConstr in particolare. Insieme a {-# LANGUAGE DeriveDataTypeable #-} che ti porterà una soluzione 1-line che funziona per qualsiasi tipo che è un'istanza di Data.Data. Non è necessario capire tutti SYB!

Se, per qualche motivo (bloccato con abbracci?), Che non è un'opzione, quindi ecco un trucco molto brutto e molto lento. Funziona solo se il tipo di dati è Showable. (Ad esempio utilizzando deriving (Show) - il che significa che nessun tipo di funzione all'interno, per esempio)

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

constrT ottiene la rappresentazione di stringa del costruttore esterno di un valore T mostrando esso, tagliarlo in parole e quindi ottenere il primo. Io do una firma esplicita del tipo in modo da non siete tentati di usarlo su altri tipi (e di eludere la restrizione monomorfismo).

Alcuni svantaggi notevoli:

  • Questo rompe terribilmente quando il tipo ha costruttori infissa (come ad esempio data T2 = Eta Int | T2 :^: T2)
  • Se alcuni dei costruttori hanno un prefisso in comune, questo sta per arrivare più lento, come una parte maggiore delle stringhe deve essere confrontato.
  • non funziona su tipi con un show personalizzato, come molti tipi di libreria.

Detto questo, è Haskell 98 ... ma questo è l'unica cosa bella che posso dire su di esso!

Altri suggerimenti

Un altro modo possibile:

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

Un errore di runtime non è l'ideale, ma meglio che dare in silenzio la risposta sbagliata.

Sarà necessario utilizzare una libreria farmaci generici come rottami tuo Boilerplate o UniPlate per fare questo in generale.

Se non si desidera essere così mano pesante, è possibile utilizzare la soluzione di Dave Hinton, insieme con la scorciatoia record vuoto:

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

Quindi non c'è bisogno di sapere quanti args ogni costruttore ha. Ma lascia ovviamente ancora a desiderare.

In alcuni casi, "Scrap Your Boilerplate" contribuirà biblioteca.

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

Si può sicuramente utilizzare i farmaci generici per eliminare il boilerplate. Il codice è un esempio da manuale perché io (e molti altri non usare il carattere jolly _ a livello alto). Mentre è noioso per scrivere tutti i casi, è meno noioso che fare con gli insetti.

In questo esempio sarei felice non solo usare la soluzione di Dave Hinton ma sarebbe uno schiaffo un pragma INLINE sulla funzione ausiliaria f.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top