Wcześniej wyjaśnił, dlaczego wyjątki .NET zachowują się inaczej niż wyjątki OCaml. Ogólnie, wyjątki .NET są odpowiednie tylko dla wyjątkowych sytuacji i są przeznaczone do tego celu. OCaml ma bardziej lekki model i dlatego są wykorzystywane do realizacji niektórych schematów sterowania.
Aby dać konkretny przykład, w OCaml można użyć wyjątku do wykonania przerwania pętli. Załóżmy na przykład, że masz funkcję test
, która sprawdza, czy liczba jest numerem, który chcemy. Poniższe iteracje nad numerami od 1 do 100 i zwrotów pierwszy pasujących numer:
// Simple exception used to return the result
exception Returned of int
try
// Iterate over numbers and throw if we find matching number
for n in 0 .. 100 do
printfn "Testing: %d" n
if test n then raise (Returned n)
-1 // Return -1 if not found
with Returned r -> r // Return the result here
zaimplementować to bez wyjątków, masz dwie opcje. Można napisać rekurencyjną funkcję, która ma takie samo zachowanie - jeśli zadzwonisz find 0
(i to jest kompilowany do zasadniczo tego samego kodu IL, jak przy użyciu return n
wewnątrz for
pętli w C#):
let rec find n =
printfn "Testing: %d" n
if n > 100 then -1 // Return -1 if not found
elif test n then n // Return the first found result
else find (n + 1) // Continue iterating
Kodowanie za pomocą funkcji rekurencyjnych możliwe być nieco dłuższy, ale możesz również użyć standardowych funkcji udostępnianych przez bibliotekę F #. Jest to często najlepszy sposób na przepisanie kodu, który używałby wyjątków OCaml do sterowania przepływem. W tym przypadku, można napisać:
// Find the first value matching the 'test' predicate
let res = seq { 0 .. 100 } |> Seq.tryFind test
// This returns an option type which is 'None' if the value
// was not found and 'Some(x)' if the value was found.
// You can use pattern matching to return '-1' in the default case:
match res with
| None -> -1
| Some n -> n
Jeśli nie jesteś zaznajomiony z typów opcji, a następnie zapoznać się z jakimś materiałem wprowadzającym. F# wikibook has a good tutorial i MSDN documentation has useful examples też.
Użycie odpowiedniej funkcji z modułu Seq
często powoduje, że kod jest znacznie krótszy, więc jest to preferowane. Może być nieco mniej wydajne niż użycie rekurencji bezpośrednio, ale w większości przypadków nie musisz się tym martwić.
EDIT: Byłem ciekawy rzeczywistej wydajności. Wersja korzystająca z Seq.tryFind
jest bardziej wydajna, jeśli dane wejściowe są leniwie generowane w sekwencji seq { 1 .. 100 }
zamiast listy [ 1 .. 100 ]
(ze względu na koszty przydzielania listy). Z tych zmian, a test
funkcji zwracającej 25 elementu, czas potrzebny do uruchomienia kodu 100000 razy na moim komputerze jest:
exceptions 2.400sec
recursion 0.013sec
Seq.tryFind 0.240sec
Jest to niezwykle trywialny przykład, więc myślę, że rozwiązanie z wykorzystaniem Seq
nie będzie generalnie uruchomić 10 razy wolniej niż równoważny kod napisany przy użyciu rekursji. Zwolnienie prawdopodobnie wynika z alokacji dodatkowych struktur danych (obiekt reprezentujący sekwencję, zamknięcia, ...), a także z powodu dodatkowego pośrednictwa (kod wymaga licznych wywołań metod wirtualnych, zamiast zwykłych operacji numerycznych i skoków). Jednak wyjątki są jeszcze bardziej kosztowne i nie powodują, że kod jest krótszy lub bardziej czytelny w żaden sposób ...
Była [dyskusja na temat lekkości wyjątków OCaml] (http://stackoverflow.com/questions/8564025/ocaml-internals-exceptions) –
@JeffreyScofield dziękuję, jednak bardziej interesują mnie aspekty dotyczące CLR . – gliderkite