2011-12-14 15 views
5

Eventuali duplicati:
How to create a type bounded within a certain rangevalori Limitazione a costruttori di tipo

ho il tipo di dati:

data Expr = Num Int 
      | Expression Expr Operator Expr 

Nel contesto del problema, i numeri che (Num Int) rappresenterà solo una cifra. C'è un modo per garantire questa restrizione all'interno della dichiarazione del tipo?

Ovviamente potremmo definire una funzione per verificare se lo Expr è valido, ma sarebbe bello che il sistema di tipi lo gestisca.

risposta

12

È possibile utilizzare un tipo di dato astratto con un smart constructor:

newtype Digit = Digit { digitVal :: Int } 
    deriving (Eq, Ord, Show) 

mkDigit :: Int -> Maybe Digit 
mkDigit n 
    | n >= 0 && n < 10 = Just (Digit n) 
    | otherwise = Nothing 

Se si mette questo in un altro modulo e non si esporta il costruttore Digit, quindi codice client non può costruire valori di tipo Digit al di fuori dell'intervallo [0,9], ma è necessario eseguire manualmente il wrapping e scartarlo per utilizzarlo. È possibile definire un'istanza Num che esegue l'aritmetica modulare, se ciò sarebbe utile; che ti permetterebbe anche di utilizzare valori letterali numerici per costruire cifre. (Allo stesso modo per Enum e Bounded.)

Tuttavia, questo non garantisce che non si tenta per creare una cifra non valida, solo che non avete mai fai. Se vuoi più garanzie, allora la soluzione manuale offerta da Jan è migliore, al costo di essere meno conveniente. (E se si definisce un'istanza Num per quel tipo di Digit, finirà proprio come "non sicuro", perché devi essere in grado di scrivere 42 :: Digit grazie al supporto letterale numerico che ti aspetteresti.)

(Se non si sa che cosa newtype è, è fondamentalmente data per tipi di dati con un singolo, campo rigorosa;. un wrapper newtype intorno T avrà la stessa rappresentazione runtime come T E 'fondamentalmente solo un'ottimizzazione, in modo da poter fingere che dice data allo scopo di capire questo.

Modifica: Per la soluzione al 100% orientata alla teoria, vedere la sezione di commento piuttosto limitata di questa risposta.

+0

Quello farebbe il lavoro, ma il sistema di tipi non è ciò che sta gestendo il problema. Questo è più quello che sto andando. –

+2

A destra: la garanzia aggiuntiva qui è che nessun valore di tipo 'Digit' può essere non valido, al contrario della semplice convalida di un' Expr' dopo il fatto. Si noti che anche la soluzione di enumerazione manuale consente valori come "error" oops ":: Digit'. Ci sono modi per gestire queste cose con forza e convenienza nei sistemi di tipi avanzati, ma non hanno ancora fatto il loro ingresso in Haskell, quindi un compromesso come questo potrebbe essere la soluzione migliore. (Non che siano entrati in qualsiasi altra lingua del "mondo reale".) – ehird

+0

Se il sistema di tipi stava gestendo il problema, non avrei dovuto convalidare 'Expr' perché non sarebbe stato compilato nel primo posto, giusto? –

5

Dato che ci sono solo dieci possibilità, è possibile utilizzare Enum per specificare tutte.

data Digit = Zero | One | Two deriving (Enum, Show) 

Allora dovreste usare fromEnum di trattarli come numeri.

1 == fromEnum One 

Allo stesso modo, utilizzando toEnum è possibile ottenere un Digit da un numero.

toEnum 2 :: Digit 

Siamo in grado di andare anche oltre e realizzare Num.

data Digit = Zero | One | Two deriving (Enum, Show, Eq) 

instance Num Digit where 
    fromInteger x = toEnum (fromInteger x) :: Digit 
    x + y = toEnum $ fromEnum x + fromEnum y 
    x * y = toEnum $ fromEnum x * fromEnum y 
    abs = id 
    signum _ = 1 

Zero + 1 + One == Two 
Problemi correlati