Несколько выходов от функции F#

Рычаг в AppDomain. Событие OnAssemblyResolve и фиксация имена сборки

private System.Reflection.Assembly OnAssemblyResolve( System.Object sender, System.ResolveEventArgs reArgs )
{
     foreach( System.Reflection.Assembly assembly in System.AppDomain.CurrentDomain.GetAssemblies() ) 
     {
         System.Reflection.AssemblyName assemblyName = assembly.GetName();
         if( assemblyName.FullName == reArgs.Name ) 
         {
              return( assembly );
         }
     }
}

источник: http://osdir.com/ml/windows.devel.dotnet.clr/2003-12/msg00441.html

12
задан Onorio Catenacci 22 October 2009 в 20:46
поделиться

4 ответа

In my opinion, match expressions are the F# analogue of early-exit for calling out erroneous conditions and handling them separately. For your example, I'd write:

 [<EntryPoint>]
 let main (args:string[]) =
     printfn "args.Length is %d" args.Length
     match args with
     | [| searchstring; filespace |] -> 
       // much code here ...
       int Success
     | _ -> printfn "Two arguments must be passed"
       int WrongNumberOfArgumentsPassed

This separates the error case nicely. In general, if you need to exit from the middle of something, split functions and then put the error case in a match. There's really no limit to how small functions should be in a functional language.

As an aside, your use of discriminated unions as sets of integer constants is a little weird. If you like that idiom, be aware that you don't need to include the type name when referring to them.

5
ответ дан 2 December 2019 в 07:03
поделиться

In F#, everything's made up of expressions (whereas in many other languages, the key building block is a statement). There's no way to exit a function early, but often this isn't needed. In C, you have an if/else blocks where the branches are made up of statements. In F#, there's an if/else expression, where each branch evaluates to a value of some type, and the value of the entire if/else expression is the value of one branch or the other.

So this C++:

int func(int param) {
  if (param<0)
    return BadParam;
  return Success;
}

Looks like this in F#:

let func param =
  if (param<0) then
    BadParam
  else
    Success

Your code is on the right track, but you can refactor it, putting most of your logic in the else branch, with the "early return" logic in the if branch.

10
ответ дан 2 December 2019 в 07:03
поделиться

Прежде всего, как уже отмечали другие, это не "способ F #" (ну, правда, не с помощью FP). Поскольку вы имеете дело не с операторами, а только с выражениями, вырваться не из чего. В основном, это обрабатывается вложенной цепочкой операторов if .. then .. else .

Тем не менее, я определенно могу видеть, где есть достаточный потенциал указывает на то, что длинная цепочка if .. then .. else может быть не очень удобочитаемой, особенно при работе с некоторым внешним API, написанным для возврата ошибки коды, а не генерировать исключения при сбоях (например, Win32 API или какой-либо компонент COM), поэтому вам действительно нужен этот код обработки ошибок. Если так, то, в частности, в F # для этого можно написать рабочий процесс . Цепочка else может быть не очень удобочитаемой, особенно при работе с некоторым внешним API, написанным для возврата кодов ошибок, а не для выдачи исключений при сбоях (например, Win32 API или какой-либо компонент COM), поэтому вам действительно нужно код обработки ошибок. Если это так, похоже, что способ сделать это, в частности, в F # - написать для него рабочий процесс . Цепочка 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 , остальная часть блока даже не оценивается. Если блок течет "с конца" без возврата , вы получите исключение во время выполнения (я пока не вижу способа принудительно применить это во время компиляции).

Это происходит с некоторые ограничения. Во-первых, рабочий процесс на самом деле не завершен - он позволяет использовать let , использовать , if , , а и ] для внутри, но не попробуйте .. с или попробуйте .. наконец . Это можно сделать - нужно реализовать Block.TryWith и Block. TryFinally - но я пока не могу найти для них документацию, так что для этого потребуется немного догадаться и больше времени. Я мог бы вернуться к этому позже, когда у меня будет больше времени, и добавлю их.

Во-вторых, поскольку рабочие процессы на самом деле являются просто синтаксическим сахаром для цепочки вызовов функций и лямбда-выражений - и, в частности, весь ваш код находится в лямбдах - вы не можете использовать let mutable внутри рабочего процесса. Вот почему я использовал ref и ! в приведенном выше примере кода, что является универсальным решением проблемы.

Наконец, есть неизбежное снижение производительности из-за всех лямбда-выражений. звонки. Предположительно, F # лучше оптимизирует такие вещи, чем, скажем, C # (который просто оставляет все как есть в IL), и может встраивать данные на уровне IL и выполнять другие трюки; но я мало что знаю об этом,

4
ответ дан 2 December 2019 в 07:03
поделиться

Эта рекурсивная функция Фибоначчи имеет две точки выхода:

let rec fib n =
  if n < 2 then 1 else fib (n-2) + fib(n-1);;
                ^      ^
1
ответ дан 2 December 2019 в 07:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: