Ответ 1
Рид уже объяснил, почему исключения .NET ведут себя иначе, чем исключения OCaml. В общем случае исключения .NET подходят только для исключительных ситуаций и предназначены для этой цели. OCaml имеет более легкую модель и поэтому они также используют некоторые шаблоны управления потоком.
Чтобы дать конкретный пример, в OCaml вы можете использовать исключение для реализации прерывания цикла. Например, скажем, у вас есть функция test
, которая проверяет, является ли число числом, которое мы хотим. Следующие итерации над номерами от 1 до 100 и возвращают первый соответствующий номер:
// 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
Чтобы реализовать это без исключений, у вас есть два варианта. Вы можете написать рекурсивную функцию, которая имеет такое же поведение - если вы вызываете find 0
(и она скомпилирована по существу тем же самым IL-кодом, что и при использовании return n
внутри цикла for
на С#):
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
Кодирование с использованием рекурсивных функций может быть немного длинным, но вы также можете использовать стандартные функции, предоставляемые библиотекой F #. Это часто лучший способ переписать код, который будет использовать исключения OCaml для потока управления. В этом случае вы можете написать:
// 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
Если вы не знакомы с типами опций, ознакомьтесь с некоторыми вводными материалами. F # wikibook имеет хороший учебник и Документация MSDN содержит полезные примеры тоже.
Использование соответствующей функции из модуля Seq
часто делает код намного короче, поэтому он предпочтительнее. Это может быть немного менее эффективно, чем использование рекурсии напрямую, но в большинстве случаев вам не нужно беспокоиться об этом.
EDIT: Мне было интересно узнать о фактической производительности. Версия, использующая Seq.tryFind
, более эффективна, если ввод представляет собой ленивую последовательность seq { 1 .. 100 }
вместо списка [ 1 .. 100 ]
(из-за затрат на распределение списков). С этими изменениями и функцией test
, которая возвращает 25-й элемент, время, необходимое для запуска кода 100000 раз на моей машине, следующее:
exceptions 2.400sec
recursion 0.013sec
Seq.tryFind 0.240sec
Это чрезвычайно тривиальный пример, поэтому я думаю, что решение с использованием Seq
обычно не будет работать в 10 раз медленнее, чем эквивалентный код, написанный с использованием рекурсии. Замедление, вероятно, связано с распределением дополнительных структур данных (объект, представляющий последовательность, замыкания,...), а также из-за дополнительной косвенности (для этого требуется множество вызовов виртуальных методов вместо простых числовых операций и переходов). Однако исключения являются еще более дорогостоящими и не делают код короче или читабельнее каким-либо образом...