Attualmente sto imparando Haskell. Una delle motivazioni per cui ho scelto questo linguaggio è stata quella di scrivere software con un grado molto alto di robustezza, cioè funzioni completamente definite, matematicamente deterministiche e mai in crash o che producono errori. Non intendo il fallimento causato dai predicati del sistema ("sistema esaurito", "computer acceso" ecc.), Quelli non sono interessanti e possono semplicemente bloccare l'intero processo. Inoltre, non intendo comportamenti errati causati da dichiarazioni non valide (pi = 4
).Haskell robusto senza errori
Invece mi riferisco ad errori causati da stati errati che voglio rimuovere rendendo quegli stati non rappresentabili e non compilabili (in alcune funzioni) tramite una tipizzazione statica rigorosa. Nella mia mente ho chiamato queste funzioni "pure" e ho capito che il sistema di tipo forte mi avrebbe permesso di realizzare questo. Tuttavia Haskell non definisce "puro" in questo modo e consente ai programmi di bloccarsi tramite error
in qualsiasi contesto.
Why is catching an exception non-pure, but throwing an exception is pure?
Questo è del tutto accettabile e non affatto strano. Tuttavia, la cosa deludente è che Haskell non sembra fornire alcune funzionalità per vietare definizioni di funzioni che potrebbero portare a rami usando error
.
Ecco un esempio inventato il motivo per cui ho trovato questo deludente:
module Main where
import Data.Maybe
data Fruit = Apple | Banana | Orange Int | Peach
deriving(Show)
readFruit :: String -> Maybe Fruit
readFruit x =
case x of
"apple" -> Just Apple
"banana" -> Just Banana
"orange" -> Just (Orange 4)
"peach" -> Just Peach
_ -> Nothing
showFruit :: Fruit -> String
showFruit Apple = "An apple"
showFruit Banana = "A Banana"
showFruit (Orange x) = show x ++ " oranges"
printFruit :: Maybe Fruit -> String
printFruit x = showFruit $ fromJust x
main :: IO()
main = do
line <- getLine
let fruit = readFruit line
putStrLn $ printFruit fruit
main
Diciamo che sono paranoico che le funzioni pure readFruit
e printFruit
davvero non fallire a causa di stati unhanded. Si può immaginare che il codice sia per lanciare un missile pieno di astronauti che in una routine assolutamente critica ha bisogno di serializzare e non serializzare i valori della frutta.
Il primo pericolo è naturalmente che abbiamo commesso un errore nel nostro modello di corrispondenza in quanto questo ci dà i terribili stati errati che non possono essere gestiti. Fortunatamente Haskell fornisce costruito in modi per prevenire quelli, abbiamo semplicemente compilare il nostro programma con -Wall
che comprende -fwarn-incomplete-patterns
e AHA:
src/Main.hs:17:1: Warning:
Pattern match(es) are non-exhaustive
In an equation for ‘showFruit’: Patterns not matched: Peach
abbiamo dimenticato di serializzare Frutta della pesca e showFruit
avrebbe gettato un errore. Questa è una soluzione semplice, aggiungiamo semplicemente:
showFruit Peach = "A peach"
Il programma ora compila senza avvisi, pericolo evitato! Lanciamo il razzo, ma improvvisamente il programma si blocca con:
Maybe.fromJust: Nothing
Il razzo è condannato e colpisce l'oceano causato dalla seguente riga guasta:
printFruit x = showFruit $ fromJust x
Essenzialmente fromJust
ha un ramo in cui si solleva un Error
quindi non volevamo nemmeno che il programma si compilasse se provassimo ad usarlo dal printFruit
assolutamente doveva essere "super" puro. Avremmo potuto fissa che ad esempio sostituendo la linea con:
printFruit x = maybe "Unknown fruit!" (\y -> showFruit y) x
Trovo strano che Haskell decide di attuare la tipizzazione forte e rilevazione modello incompleto, tutto nel perseguimento Nobel di prevenire gli stati non validi di essere rappresentabile ma cadute proprio di fronte al traguardo non dando ai programmatori un modo per rilevare i rami su error
quando non sono consentiti.In un certo senso ciò rende Haskell meno robusto di Java, il che ti obbliga a dichiarare le eccezioni che le tue funzioni sono autorizzate a sollevare.
Il modo più semplice per implementare questo sarebbe semplicemente non definito error
in qualche modo, localmente per una funzione e qualsiasi funzione utilizzata dalla sua equazione tramite una qualche forma di dichiarazione associata. Tuttavia questo non sembra essere possibile.
The wiki page about errors vs exceptions menziona un'estensione denominata "Extended Static Checking" per questo scopo tramite contratti ma porta semplicemente a un collegamento interrotto.
Si riduce fondamentalmente a: Come faccio a ottenere il programma di cui sopra per non compilare perché utilizza fromJust
? Tutte le idee, i suggerimenti e le soluzioni sono benvenute.
(1) Credo che [questi sono i documenti che stai cercando] (http://research.microsoft.com/~simonpj/papers/verify/index.htm). (2) Si noti che 'printFruit' non è necessario. Se un utente vuole visualizzare un 'Maybe Fruit' può usare' fmap showFruit' e quindi scartare il 'Maybe' in modo ragionevole. Ovviamente questo non risponde alla tua domanda (sarebbe bene tenere gli utenti lontani da 'fromJust'!), Quindi continua :) – duplode
Mi piacerebbe che Haskell avesse una sorta di correttore di totalità. Sarei persino soddisfatto di un controllore senza eccezioni che verificasse la non esaustività e l'uso di funzioni parziali, ma non controllasse i loop infiniti. Vorrei anche sollevare questo tipo di avvertimenti in errori, in modo che non possa facilmente trascurarli. Questo probabilmente non richiederebbe un grande sforzo per implementare in GHC. – chi
Hai guardato Idris? Potrebbe essere la lingua che desideri. – user3237465