Aktualnie uczę się Haskella. Jedną z motywacji, dla których wybrałem ten język, było napisanie oprogramowania o bardzo wysokim stopniu odporności, tj. Funkcji, które są w pełni zdefiniowane, matematycznie deterministyczne i nigdy nie powodują awarii ani nie powodują błędów. Nie chodzi mi o awarię spowodowaną przez predykaty systemu ("system z pamięci", "komputer w ogniu" itp.), Nie są one interesujące i mogą po prostu załamać cały proces. Nie mam również na myśli błędnego zachowania spowodowanego przez nieważne deklaracje (pi = 4
).Solidny haskell bez błędów
Zamiast tego odnoszę się do błędów spowodowanych przez błędne stany, które chcę usunąć, czyniąc te stany niereprezentowalnymi i nie nadającymi się do skompilowania (w niektórych funkcjach) poprzez ścisłe pisanie statyczne. W moim umyśle nazwałem te funkcje "czystymi" i uznałem, że silny system typów pozwoli mi to osiągnąć. Jednak Haskell nie definiuje "czystego" w ten sposób i pozwala programom na awarię przez error
w dowolnym kontekście.
Why is catching an exception non-pure, but throwing an exception is pure?
Jest to całkowicie dopuszczalne i nie jest dziwne w ogóle. Niezadowalające jest jednak to, że Haskell nie wydaje się oferować pewnej funkcjonalności, aby zabronić definicji funkcji, które mogłyby prowadzić do oddziałów przy użyciu error
.
Oto zmyślony przykład dlaczego uważam to rozczarowujące:
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
Powiedzmy, że jestem paranoikiem, że czysta funkcje readFruit
i printFruit
naprawdę nie powiedzie się z powodu stanów niedotykane. Można sobie wyobrazić, że kodeks ma na celu wystrzelenie rakiety pełnej astronautów, która w absolutnie krytycznej rutynie potrzebuje serializacji i odseparowania wartości owoców.
Pierwszym niebezpieczeństwem jest naturalne, że popełniliśmy błąd w dopasowywaniu wzorców, ponieważ daje to nam przerażające błędne stany, których nie można obsłużyć. Na szczęście Haskell dostarcza zbudowany w taki sposób, aby zapobiec tych, po prostu skompilować nasz program z -Wall
który obejmuje -fwarn-incomplete-patterns
i AHA:
src/Main.hs:17:1: Warning:
Pattern match(es) are non-exhaustive
In an equation for ‘showFruit’: Patterns not matched: Peach
Zapomnieliśmy serializacji brzoskwini i showFruit
byłby wyrzucony błąd. To się łatwo naprawić, po prostu dodajemy:
showFruit Peach = "A peach"
Program kompiluje się bez ostrzeżenia, niebezpieczeństwo zażegnane! Uruchamiamy rakietę ale nagle program wywala się z:
Maybe.fromJust: Nothing
Rakieta jest skazany i uderza ocean spowodowane następującymi wadliwego line:
printFruit x = showFruit $ fromJust x
Zasadniczo fromJust
ma oddział, gdzie podnosi Error
więc nie chcieliśmy, aby program się kompilował, jeśli spróbujemy go użyć od printFruit
absolutnie musi być "super" czysty. Mogliśmy ustalona, że na przykład poprzez zastąpienie linii z:
printFruit x = maybe "Unknown fruit!" (\y -> showFruit y) x
Uważam to za dziwne, że Haskell zdecyduje się wprowadzić ścisłe wpisywanie i niekompletny wykrywanie wzorców, wszystko w pogoni nobel zapobiegania nieprawidłowe stany od przedstawianego ale upadki tuż przed linią mety, nie dając programistom sposobu na wykrycie gałęzi na error
, gdy nie są dozwolone.W pewnym sensie sprawia to, że Haskell jest mniej odporny niż Java, która zmusza Cię do zadeklarowania wyjątków, które twoje funkcje mogą podnieść.
Najbardziej banalnym sposobem wdrożenia tego byłoby po prostu nieokreślone error
w jakiś sposób, lokalnie dla funkcji i dowolnej funkcji używanej przez jej równanie przez jakąś formę powiązanej deklaracji. Jednak nie wydaje się to możliwe.
The wiki page about errors vs exceptions wymienia w tym celu rozszerzenie o nazwie "Extended Static Checking", ale prowadzi to po prostu do zerwania łącza.
W zasadzie sprowadza się do: Jak uzyskać powyższy program, aby nie skompilować, ponieważ używa on fromJust
? Wszystkie pomysły, sugestie i rozwiązania są mile widziane.
(1) Uważam, że [są to artykuły, których szukasz] (http://research.microsoft.com/~simonpj/papers/verify/index.htm). (2) Zwróć uwagę, że 'printFruit' jest niepotrzebny. Jeśli użytkownik chce wyświetlić 'Maybe Fruit' może użyć' fmap showFruit', a następnie rozwinąć 'Maybe' w rozsądny sposób. To oczywiście nie odpowiada na twoje pytanie (dobrze byłoby trzymać użytkowników z dala od 'fromJust' również!), Więc kontynuuj :) – duplode
Chciałbym, aby Haskell miał coś w rodzaju sprawdzania kompletności. Byłbym nawet usatysfakcjonowany sprawdzaniem bez wyjątku, sprawdzającym, czy nie ma kompletności i wykorzystania funkcji częściowych, ale nie sprawdzając nieskończonych pętli. Chciałbym również wprowadzić takie ostrzeżenia w błąd, tak że nie mogę ich łatwo przeoczyć. To prawdopodobnie nie wymagałoby dużego nakładu pracy w GHC. – chi
Czy spojrzałeś na Idris? To może być język, którego pragniesz. – user3237465