2009-10-29 21 views
21

W nadchodzącym Scala 2.8, pakiet util.control został dodany który zawiera bibliotekę przerwy i konstrukt do wyjątków manipulacji tak, że kod, który wygląda następująco:scala 2.8 control Wyjątek - o co chodzi?

type NFE = NumberFormatException 
val arg = "1" 
val maybeInt = try { Some(arg.toInt) } catch { case e: NFE => None } 

można zastąpić kodem jak:

import util.control.Exception._ 
val maybeInt = catching(classOf[NFE]) opt arg.toInt 

Moje pytanie jest dlaczego? Co to oznacza dla języka innego niż dostarczanie innego (i radykalnie odmiennego) sposobu wyrażania tego samego? Czy jest coś, co można wyrazić za pomocą nowej kontroli, ale nie za pośrednictwem try-catch? Czy jest to DSL, które powinno być czy obsługa wyjątków w Scali wygląda jak jakiś inny język (a jeśli tak, to który?)

Odpowiedz

28

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ć.

+0

* 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 ... –

+0

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. –

+0

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! :-) –

3

Powiedziałbym, że to przede wszystkim kwestia stylu wypowiedzi.

Poza tym, że jest krótszy niż odpowiednik, nowa metoda catch() oferuje bardziej funkcjonalny sposób wyrażania tego samego zachowania. Spróbuj ... oświadczenia o połowach są zazwyczaj uważane za obowiązkowe, a wyjątki są uważane za efekty uboczne. Catching() kładzie koc na tym imperatywnym kodzie, aby ukryć go przed widokiem.

Co ważniejsze, teraz, gdy mamy funkcję, łatwiej można ją skomponować z innymi rzeczami; można go przekazać do innych funkcji wyższego rzędu, aby stworzyć bardziej wyrafinowane zachowanie. (Nie można przekazać instrukcji try .. catch z parametryzowanym typem wyjątku bezpośrednio).

Innym sposobem na sprawdzenie tego jest to, czy Scala nie oferowała tej funkcji catch(). Najprawdopodobniej ludzie "wymyśliliby" go niezależnie, a to spowodowałoby powielanie kodu i doprowadziłoby do powstania niestandardowego kodu. Myślę więc, że projektanci Scali uważają, że ta funkcja jest na tyle powszechna, że ​​gwarantuje włączenie jej do standardowej biblioteki Scala. (I zgadzam)

alex

+1

można wskazuje na źródło, w którym * "wyjątki są uważane za efekty uboczne" *? W jaki sposób wyjątek (który niekoniecznie zmienia stan celu) skutkuje ubocznym? –

+0

Zgadzam się. Oczywiście jest to kwestia stylu ... ale posiadanie dwóch tak zupełnie odmiennych sposobów osiągnięcia tego samego wyniku jest moim zdaniem bardzo niebezpieczne, ponieważ doprowadzi to do problemów związanych z utrzymaniem. –

+0

Czy możesz podać przykład metody, która ma * wyłapywanie * wyrażenie, ale nie może być zaprojektowane do podjęcia 'try-catch'? –

9

Jako facet, który to napisał, powody to kompozycja i enkapsulacja. Kompilator scala (a ja stawiam na przyzwoite rozmiary baz źródłowych) jest zaśmiecony miejscami, które połknęłyby wszystkie wyjątki - można je zobaczyć za pomocą - Totn-catches - ponieważ programista jest zbyt leniwy, aby wyliczyć odpowiednie, co jest zrozumiałe ponieważ to jest denerwujące. Poprzez umożliwienie definiowania, ponownego użycia i komponowania bloków catch i finally niezależnie od logiki try, miałem nadzieję obniżyć barierę pisania rozsądnych bloków.

I to nie jest dokładnie ukończone i pracuję też nad milionem innych obszarów. Jeśli przejrzysz kod aktorów, zobaczysz przykłady ogromnych, zakrytych i wklejonych, wielokrotnie zagnieżdżonych bloków try/catch/finally. Byłem/am chcąc zadowolić try { catch { try { catch { try { catch ...

Mojego ostatecznego planu jest mieć haczyk podjąć rzeczywiste PartialFunction zamiast wymaga dosłownego listę wyciągów Case, który prezentuje zupełnie nowy poziom szans, tzn try foo() catch getPF()

+2

Aha, i zapomniałem wspomnieć o moim ewentualnym planie, aby mieć catch rzeczywiście cefunkcyjną funkcję, zamiast wymagać literalnej listy zdań, która przedstawia zupełnie nowy poziom możliwości , czyli spróbuj foo() catch getPF() – extempore

+0

jaki jest status podjęcia PartialFunction? –

+2

Wdrożony dawno temu, jest w scala 2.9.1. – extempore

Powiązane problemy