Прежде всего, как уже отмечали другие, это не «путь F #» (ну, на самом деле, не путь FP). Поскольку вы не имеете дело с утверждениями, а только с выражениями, на самом деле из этого ничего не выйдет. Обычно это обрабатывается вложенной цепочкой if
.. then
.. else
операторов.
Тем не менее, я, конечно, могу видеть, где достаточно потенциальных точек выхода, что длинная цепочка if
.. then
.. else
может быть не очень читабельной - особенно когда речь идет о каком-то внешнем API, который написан возвращать коды ошибок, а не генерировать исключения при сбоях (скажем, Win32 API или некоторый COM-компонент), так что вам действительно нужен этот код обработки ошибок. Если это так, то, похоже, способ сделать это в F #, в частности, написать для него рабочий процесс .
Вот мой первый взгляд на это:
type BlockFlow<'a> =
| Return of 'a
| Continue
type Block() =
member this.Zero() = Continue
member this.Return(x) = Return x
member this.Delay(f) = f
member this.Run(f) =
match f() with
| Return x -> x
| Continue -> failwith "No value returned from block"
member this.Combine(st, f) =
match st with
| Return x -> st
| Continue -> f()
member this.While(cf, df) =
if cf() then
match df() with
| Return x -> Return x
| Continue -> this.While(cf, df)
else
Continue
member this.For(xs : seq<_>, f) =
use en = xs.GetEnumerator()
let rec loop () =
if en.MoveNext() then
match f(en.Current) with
| Return x -> Return x
| Continue -> loop ()
else
Continue
loop ()
member this.Using(x, f) = use x' = x in f(x')
let block = Block()
Пример использования:
open System
open System.IO
let n =
block {
printfn "Type 'foo' to terminate with 123"
let s1 = Console.ReadLine()
if s1 = "foo" then return 123
printfn "Type 'bar' to terminate with 456"
let s2 = Console.ReadLine()
if s2 = "bar" then return 456
printfn "Copying input, type 'end' to stop, or a number to terminate with that number"
let s = ref ""
while (!s <> "end") do
s := Console.ReadLine()
let (parsed, n) = Int32.TryParse(!s)
if parsed then
printfn "Dumping numbers from 1 to %d to output.txt" n
use f = File.CreateText("output.txt") in
for i = 1 to n do
f.WriteLine(i)
return n
printfn "%s" s
}
printfn "Terminated with: %d" n
Как видите, он эффективно определяет все конструкции таким образом, что, как только встречается return
, остальная часть блока даже не оценивается. Если блок течет «с конца» без return
, вы получите исключение времени выполнения (пока я не вижу способа применить это во время компиляции).
Это имеет некоторые ограничения. Прежде всего, рабочий процесс действительно не завершен - он позволяет вам использовать let
, use
, if
, while
и for
внутри, но не try
.. with
или try
.. finally
. Это может быть сделано - вам нужно реализовать Block.TryWith
и Block.TryFinally
- но я пока не могу найти документы для них, так что для этого потребуется немного угадать и больше времени. Я мог бы вернуться к нему позже, когда у меня будет больше времени, и добавить их.
Во-вторых, поскольку рабочие процессы на самом деле являются просто синтаксическим сахаром для цепочки вызовов функций и лямбд - и, в частности, весь ваш код находится в лямбдах - вы не можете использовать let mutable
внутри рабочего процесса. Вот почему я использовал ref
и !
в приведенном выше примере кода, который является обходным решением общего назначения.
Наконец, есть неизбежное снижение производительности из-за всех лямбда-вызовов. Предположительно, F # лучше оптимизирует такие вещи, чем, скажем, C # (который просто оставляет все как в IL), и может встроить что-то на уровне IL и делать другие трюки; но я не очень разбираюсь в этом, поэтому точный удар по производительности, если таковой имеется, можно определить только по профилированию.