È 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.
Quello farebbe il lavoro, ma il sistema di tipi non è ciò che sta gestendo il problema. Questo è più quello che sto andando. –
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
Se il sistema di tipi stava gestendo il problema, non avrei dovuto convalidare 'Expr' perché non sarebbe stato compilato nel primo posto, giusto? –