Ответ 1
(Отредактировано этот пост на fshub)
В первый раз, когда я отправился на перерыв/продолжить в OCaml/F #, он бросил меня для (бесконечного) цикла, так сказать, потому что такого не существует! В OCaml можно использовать исключения для выхода из цикла, потому что они очень дешевые, но в F # (в .NET) накладные расходы довольно высоки и не полезны для "нормального" управления потоком.
Это появилось, когда вы играли с алгоритмами сортировки некоторое время назад (чтобы убить некоторое время), которые сильно используют повтор/до и ломаются. Меня поразило то, что рекурсивные функции вызова хвоста могут достичь точно такого же результата, только с легким умением до читаемости. Итак, я выбросил "изменчивый bDone" и "пока не bDone" и попытался написать код без этих императивных конструкций. Ниже перечислены только фрагменты цикла и показано, как писать повтор/пока, do/while, while/do, break/continue и test-in-the-middle стиль кода с использованием tailcalls. Кажется, что все они работают с той же скоростью, что и обычный оператор F # 'while', но ваш пробег может отличаться (некоторые платформы могут неправильно реализовать tailcall и, следовательно, могут сбрасывать ошибки до тех пор, пока они не будут исправлены). В конце это алгоритм (плохой) сортировки, написанный в обоих стилях, для сравнения.
Начнем с цикла "do/while", написанного в традиционном стиле F #, а затем рассмотрим функциональные вариации, которые обеспечивают как один и тот же тип цикла, так и различные семантики, такие как while/do, repeat/until, test в середине и даже break/continue (без monads.. um, workflow!).
#light
(* something to work on... *)
let v = ref 0
let f() = incr v;
let g() = !v;
let N = 100000000
let imperDoWhile() =
let mutable x = 0
let mutable bDone = false
while not bDone do
f()
x <- x + 1
if x >= N then bDone <- true
Хорошо, это достаточно легко. Имейте в виду, что f() всегда вызывается хотя бы один раз (do/while).
Вот тот же код, но в функциональном стиле. Обратите внимание: нам не нужно объявлять здесь mutable.
let funDoWhile() =
let rec loop x =
f() (*Do*)
if x < N then (*While*)
loop (x+1)
loop 0
Мы можем открутить это до традиционного do/while, поместив вызов функции внутри блока if.
let funWhileDo() =
let rec loop x =
if x < N then (*While*)
f() (*Do*)
loop (x+1)
loop 0
Как насчет повторения блока до тех пор, пока какое-либо условие не будет истинным (повторить/пока)? Легко...
let funRepeatUntil() =
let rec loop x =
f() (*Repeat*)
if x >= N then () (*Until*)
else loop (x+1)
loop 0
Что случилось с монашеским перерывом? Ну, просто введите условное выражение, которое возвращает "unit", как в:
let funBreak() =
let rec loop() =
let x = g()
if x > N then () (*break*)
else
f()
loop()
loop()
Как насчет продолжения? Ну, это просто еще один звонок в петлю! Во-первых, с синтаксическим костылем:
let funBreakContinue() =
let break' () = ()
let rec continue' () =
let x = g()
if x > N then break'()
elif x % 2 = 0 then
f(); f(); f();
continue'()
else
f()
continue'()
continue'()
А потом снова без (уродливого) синтаксического костыля:
let funBreakContinue'() =
let rec loop () =
let x = g()
if x > N then ()
elif x % 2 = 0 then
f(); f(); f();
loop()
else
f()
loop ()
loop()
Просто как пирог!
Одним из приятных результатов этих форм циклов является то, что он упрощает определение и реализацию состояний в ваших циклах. Например, сортировка пузырьков непрерывно циклически перемещается по всему массиву, заменяя значения, которые неуместны по мере их нахождения. Он отслеживает, прошел ли пропуск по массиву любые обмены. Если нет, то каждое значение должно быть в нужном месте, поэтому сортировка может завершиться. В качестве оптимизации на каждом проходе через массив последнее значение в массиве заканчивается сортировкой в нужное место. Таким образом, цикл может быть сокращен каждый раз через. Большинство алгоритмов проверяют своп и обновляют флаг "bModified" каждый раз, когда он есть. Однако, как только первый обмен выполняется, нет необходимости в этом назначении; он уже установлен в true!
Вот код F #, который реализует сортировку пузыря (да, тип пузыря - ужасный алгоритм, quicksort rock). В конце - императивная реализация, которая не меняет состояние; он обновляет флаг bModified для каждого обмена. Интересно, что императивное решение быстрее на крошечных массивах и на два процента меньше на больших. (Сделано для хорошего примера, хотя).
let inline sort2 f i j (a:'a array) =
let i' = a.[ i ]
let j' = a.[ j ]
if f i' j' > 0 then
a.[ i ] <- j'
a.[ j ] <- i'
let bubble f (xs:'a array) =
if xs.Length = 0
then ()
let rec modified i endix =
if i = endix then
unmodified 0 (endix-1)
else
let j = i+1
sort2 f i j xs
modified j endix
and unmodified i endix =
if i = endix then
()
else
let j = i+1
let i' = xs.[ i ]
let j' = xs.[ j ]
if f i' j' > 0 then
xs.[ i ] <- j'
xs.[ j ] <- i'
modified j endix
else
unmodified j endix
in unmodified 0 (xs.Length-1)
let bubble_imperitive f (xs:'a array) =
let mutable bModified = true
let mutable endix = xs.Length - 1
while bModified do
bModified <- false
endix <- endix - 1
for i in 0..endix do
let j = i+1
let i' = xs.[ i ]
let j' = xs.[ j ]
if f i' j' > 0 then
xs.[ i ] <- j'
xs.[ j ] <- i'
bModified <- true
done
done