Jak działają wyjątki w Haskell?

86

W GHCi:

Prelude> error (error "")
*** Exception: 
Prelude> (error . error) ""
*** Exception: *** Exception: 

Dlaczego pierwszy nie jest zagnieżdżonym wyjątkiem?

Daniel Wagner
źródło
9
Oto transformacja, której GHC może dokonać: „Jestem Kompilatorem, który działa samodzielnie i wszystkie _ | _ są do mnie podobne”. Czy pytasz o szczegóły implementacji, które powodują, że te dwie linie kompilują się inaczej?
shachaf
3
errorjest specjalny i nie jest tak naprawdę mechanizmem wyjątków. Aby poznać prawdziwe, możliwe do złapania wyjątki, zobacz monadę Error.
Cat Plus Plus,
1
Zauważ na przykład, że (\f g x -> f (g x)) error error ""zachowuje się inaczej niż (.) error error "", mimo że ta funkcja jest równoważna (.). Może ma to związek z flagami optymalizacji, z którymi skompilowano program Prelude.
shachaf
5
A także iterate error "" !! nniesamowite fix error.
Vitus
9
Zawsze to udaję error = errori odpowiednio programuję.
Gabriel Gonzalez

Odpowiedzi:

101

Odpowiedź brzmi, że jest to (nieco zaskakująca) semantyka nieprecyzyjnych wyjątków

Gdy można wykazać, że czysty kod oblicza zbiór wyjątkowych wartości (tj. Wartość errorlub undefined, a wyraźnie nie rodzaj wyjątków wygenerowanych w IO ), wówczas język zezwala na zwrócenie dowolnej wartości tego zestawu. Wyjątkowe wartości w Haskell są bardziej jak NaNw kodzie zmiennoprzecinkowym, a nie w wyjątkach opartych na przepływie sterowania w językach imperatywnych.

Od czasu do czasu nawet zaawansowanych Haskellerów może się zdarzyć:

 case x of
   1 -> error "One"
   _ -> error "Not one"

Ponieważ kod ocenia się jako zestaw wyjątków, GHC może wybrać jeden. Przy włączonych optymalizacjach może się okazać, że zawsze daje to wynik „Brak”.

Dlaczego to robimy? Ponieważ w przeciwnym razie nadmiernie ograniczylibyśmy kolejność ocen języka, np. Musielibyśmy ustalić deterministyczny wynik dla:

 f (error "a") (error "b")

na przykład wymagając, aby był oceniany od lewej do prawej, jeśli obecne są wartości błędów. Bardzo nie-haskelly!

Ponieważ nie chcemy paraliżować optymalizacji, które można wykonać w naszym kodzie tylko w celu wsparcia error, rozwiązaniem jest określenie, że wynik jest niedeterministycznym wyborem z zestawu wyjątkowych wartości: nieprecyzyjne wyjątki! W pewnym sensie wszystkie wyjątki są zwracane i jeden jest wybierany.

Zwykle nic Cię to nie obchodzi - wyjątek jest wyjątkiem - chyba że zależy Ci na ciągu znaków wewnątrz wyjątku, w którym to przypadku użycie errordo debugowania jest bardzo mylące.


Odniesienia: semantyka nieprecyzyjnych wyjątków , Simon Peyton Jones, Alastair Reid, Tony Hoare, Simon Marlow, Fergus Henderson. Projektowanie i wdrażanie języków programowania proc (PLDI'99), Atlanta. ( PDF )

Don Stewart
źródło
2
Rozumiem, że GHC wybiera jeden z wyjątków, które można napotkać. Ale w twoim przykładzie „przypadku” wyjątek „Brak” nie może zostać napotkany dla wejścia równego 1, więc nadal klasyfikuję to jako błąd.
Peaker
8
@Peaker dead code elim - optymalizator nie musi patrzeć na x, aby zobaczyć, że wynikiem jest błąd, wszystkie gałęzie generują „tę samą” wartość, więc może całkowicie zignorować wartość wejściową. To nie błąd, z nieprecyzyjnymi wyjątkami!
Don Stewart
1
@lpsmith: Myślę, że wszystkie typy wyjątków są nieprecyzyjne (gdy są wyrzucane przy użyciu throw) i można deterministycznie zgłosić wyjątek z throwIO.
FunctorSalad
4
@Peaker Myślę, że masz rację. Myślę, że ghc nie powinien optymalizować tego wyrażenia, jeśli chce grać według zasad przedstawionych w nieprecyzyjnym dokumencie dotyczącym wyjątków.
sierpień
3
Co papier wyjątki nieprecyzyjne nie wydają się powiedzieć to, że jeśli sprawa scrutinee jest wartością błędu mogą być zwrócone następnie wartości błędu z gałęzi. Więc case error "banana" of (x:xs) -> error "bonobo"mogę ci dać * Exception: bonobo.
Ben Millwood