Istnieją dwa sposoby, aby myśleć o wyjątkami. Jednym ze sposobów jest uważanie ich za kontrolę przepływu: wyjątek zmienia przepływ programu, powodując skok egzekucji z jednego miejsca do drugiego. Drugi sposób to myśleć o nich jako o danych: wyjątkiem jest informacja o wykonaniu programu, który może być następnie wykorzystany jako wkład do innych części programu.
Model try
/catch
stosowany w językach C++ i Java jest bardzo podobny do pierwszego rodzaju (*).
Jeśli jednak wolisz zajmować się wyjątkami jako danymi, musisz odwołać się do kodu takiego, jak ten pokazany. W tym prostym przypadku jest to raczej łatwe. Jednak jeśli chodzi o styl funkcjonalny, w którym króluje kompozycja, sprawy zaczynają się komplikować. Musisz albo duplikować kod dookoła, albo toczyć własną bibliotekę, aby sobie z tym poradzić.
W związku z tym, w języku, który ma wspierać zarówno styl funkcjonalny, jak i OO, nie należy się dziwić, że biblioteka obsługuje przetwarzanie wyjątków jako danych.
Należy zauważyć, że istnieje wiele innych możliwości dostępnych w ramach obsługi Exception
. Możesz, na przykład, obsługiwać programy przechwytujące łańcuchy, w sposób, w jaki funkcje częściowe w łańcuchu windowania ułatwiają delegowanie odpowiedzialności za obsługę żądań stron internetowych.
Oto jeden z przykładów tego, co można zrobić, ponieważ automatyczne zarządzanie zasobami jest w modzie w tych dniach:
def arm[T <: java.io.Closeable,R](resource: T)(body: T => R)(handlers: Catch[R]):R = (
handlers
andFinally (ignoring(classOf[Any]) { resource.close() })
apply body(resource)
)
co daje bezpieczne zamknięcie zasobu (Zwróć uwagę na użycie ignorując), a jeszcze stosuje dowolną logikę łowienia, której możesz użyć.
(*) Co ciekawe, kontrola wyjątków Fortha, catch
& , jest ich połączeniem. Skok przepływu od throw
do catch
, ale wtedy ta informacja jest traktowana jako dane.
EDIT
Ok, ok, wydajność. Dam przykład. JEDEN przykład i to wszystko! Mam nadzieję, że to nie jest zbyt wymyślne, ale nie da się tego obejść. Tego rodzaju rzeczy byłyby najbardziej przydatne w dużych strukturach, a nie w małych próbkach.
W każdym razie najpierw ustalmy, co należy zrobić z zasobem. Zdecydowałem się na drukowanie linii i powrót liczbę wierszy drukowanych, a oto kod:
def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
var lineNumber = 0
var lineText = lnr.readLine()
while (null != lineText) {
lineNumber += 1
println("%4d: %s" format (lineNumber, lineText))
lineText = lnr.readLine()
}
lineNumber
} _
Oto typ tej funkcji:
linePrinter: (lnr: java.io.LineNumberReader)(util.control.Exception.Catch[Int]) => Int
Więc arm
otrzymała ogólną zamykane, ale Potrzebuję LineNumberReader, więc kiedy zadzwonię do tej funkcji, muszę to przekazać. Zwracam jednak funkcję Catch[Int] => Int
, co oznacza, że muszę przekazać dwa parametry do linePrinter
, aby uruchomić ją. Załóżmy wymyślić Reader
, obecnie:
val functionText = """def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
var lineNumber = 1
var lineText = lnr.readLine()
while (null != lineText) {
println("%4d: %s" format (lineNumber, lineText))
lineNumber += 1
lineText = lnr.readLine()
}
lineNumber
} _"""
val reader = new java.io.LineNumberReader(new java.io.StringReader(functionText))
Więc teraz, użyjmy go. Po pierwsze, prosty przykład:
scala> linePrinter(new java.io.LineNumberReader(reader))(noCatch)
1: def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
2: var lineNumber = 1
3: var lineText = lnr.readLine()
4: while (null != lineText) {
5: println("%4d: %s" format (lineNumber, lineText))
6: lineNumber += 1
7: lineText = lnr.readLine()
8: }
9: lineNumber
10: } _
res6: Int = 10
A jeśli próbuję go ponownie, otrzymuję to:
scala> linePrinter(new java.io.LineNumberReader(reader))(noCatch)
java.io.IOException: Stream closed
Załóżmy teraz chcę wrócić 0 jeśli każdy wyjątek dzieje. Mogę to zrobić tak:
linePrinter(new java.io.LineNumberReader(reader))(allCatch withApply (_ => 0))
Co ciekawe jest to, że ja całkowicie oddzielona wyjątek obsługi (The catch
część try
/catch
) od zamknięcia zasobu, która jest wykonywana przez finally
. Ponadto obsługa błędów jest wartością, którą mogę przekazać do funkcji. Co najmniej, sprawia, że ośmieszanie instrukcji o wiele łatwiejsze. :-)
Ponadto mogę połączyć wiele różnych metod: Catch
przy użyciu metody or
, aby różne warstwy kodu mogły dodawać różne procedury obsługi dla różnych wyjątków. Co naprawdę jest moim głównym celem, ale nie mogłem znaleźć bogatego w wyjątki interfejsu (w krótkim czasie wyglądałem :).
Zakończę uwagą dotyczącą definicji podanej arm
. To nie jest dobre.W szczególności nie mogę użyć metod Catch
, takich jak toEither
lub toOption
, aby zmienić wynik z R
na coś innego, co poważnie zmniejsza wartość używania w tym celu wartości Catch
. Nie jestem jednak pewien, jak to zmienić.
* Daniel * - jak zawsze przemyślana odpowiedź, ale czy możesz przedłużyć odpowiedź, aby podać 2 różne przykłady nazywania swojej metody "ręki"? Widzę, że możesz dostarczyć różne programy obsługi, ale możesz to zrobić w trybie imperatywnym, wychwytując wyjątki na stronie połączenia ... –
Wszystkie pełne wersje językowe są równoważne. Możesz zrobić "filtrowanie" bez funkcji wyższego rzędu, to jest po prostu bardziej niezręczne. Spróbuję wymyślić dobry przykład, aby zilustrować, jak to jest inne. –
Na marginesie, uniknąłem napisania przykładu na pierwszym miejscu, ponieważ interfejsy API Java, które implementują Closeable, są okropne. Nie chciałem przejść przez ból! :-) –