13

I could do this easily in C++ (note: I didn't test this for correctness--it's only to illustrate what I'm trying to do):

   const int BadParam = -1;
   const int Success = 0;

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

      //normal processing

      return Success;
   }

But I cannot figure out how to exit a routine early in F#. What I want to do is to exit the function on a bad input but continue if the input is ok. Am I missing some fundamental property of F# or am I approaching the problem in the wrong way since I'm just learning FP? Is a failwith my only option here?

This is what I've got so far and it compiles ok:

   #light

   module test1

       (* Define how many arguments we're expecting *)
       let maxArgs = 2;;
       (* The indices of the various arguments on the command line *)
       type ProgArguments =
           | SearchString = 0
           | FileSpec = 1;;

       (* Various errorlevels which the app can return and what they indicate *)
       type ProgReturn =
           | Success = 0
           | WrongNumberOfArgumentsPassed = 1;;

       [<EntryPoint>]
       let main (args:string[]) =

           printfn "args.Length is %d" args.Length

           let ProgExitCode = if args.Length <> maxArgs then
                                   printfn "Two arguments must be passed"
                                   int ProgReturn.WrongNumberOfArgumentsPassed
                                   (* Want to exit "main" here but how? *)
                               else
                                   int ProgReturn.Success

           let searchstring, filespec  = args.[int ProgArguments.SearchString],args.[int ProgArguments.FileSpec];

           printfn "searchstring is %s" searchstring
           printfn "filespec is %s" filespec

           ProgExitCode;;

Is there an FP way of dealing with this sort of thing?

Onorio Catenacci
  • 14,928
  • 14
  • 81
  • 132

7 Answers7

11

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.

kvb
  • 54,864
  • 2
  • 91
  • 133
  • @kvb Thanks for the response. I think that you've probably hit on a very good approach. – Onorio Catenacci Oct 22 '09 at 22:10
  • 1
    Of course there is a way of multiple exit points: Using an exit continuation in continuation passing style - Very common in Haskell and also possible in F#! – Dario Dec 06 '09 at 19:54
6

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.

Nathan Shively-Sanders
  • 18,329
  • 4
  • 46
  • 56
  • Nathan, Thanks--that last comment is kind of interesting because I did try this without the type name and it failed. Only worked with the type name specified. – Onorio Catenacci Oct 22 '09 at 22:01
  • Strange. When you use a discriminated union like an enum, it requires the full namespace. In your situation (you don't pattern match on the types), I would just declare a set of integer constants. – Nathan Shively-Sanders Oct 22 '09 at 22:37
  • @Nathan Sanders, actually that's what I had done initially--a set of integer constants. But they did seem more like an enumerated set of possible returns than a series of constants. 6 of one, 1/2 dozen of another I guess. Still it's good to know what's not considered idiomatic when one is trying to learn a new language. – Onorio Catenacci Oct 23 '09 at 00:56
5

First of all, as others have already noted, it's not "the F# way" (well, not FP way, really). Since you don't deal with statements, but only expressions, there isn't really anything to break out of. In general, this is treated by a nested chain of if..then..else statements.

That said, I can certainly see where there are enough potential exit points that a long if..then..else chain can be not very readable - especially so when dealing with some external API that's written to return error codes rather than throw exceptions on failures (say Win32 API, or some COM component), so you really need that error handling code. If so, it seems the way to do this in F# in particular would be to write a workflow for it. Here's my first take at it:

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() 

Usage sample:

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

As you can see, it effectively defines all constructs in such a way that, as soon as return is encountered, the rest of the block is not even evaluated. If block flows "off the end" without a return, you'll get a runtime exception (I don't see any way to enforce this at compile-time so far).

This comes with some limitations. First of all, the workflow really isn't complete - it lets you use let, use, if, while and for inside, but not try..with or try..finally. It can be done - you need to implement Block.TryWith and Block.TryFinally - but I can't find the docs for them so far, so this will need a little bit of guessing and more time. I might come back to it later when I have more time, and add them.

Second, since workflows are really just syntactic sugar for a chain of function calls and lambdas - and, in particular, all your code is in lambdas - you cannot use let mutable inside the workflow. It's why I've used ref and ! in the sample code above, which is the general-purpose workaround.

Finally, there's the inevitable performance penalty because of all the lambda calls. Supposedly, F# is better at optimizing such things than, say C# (which just leaves everything as is in IL), and can inline stuff on IL level and do other tricks; but I don't know much about it, so the exact performance hit, if any, could only be determined by profiling.

Pavel Minaev
  • 99,783
  • 25
  • 219
  • 289
  • @Pavel Minaev, I hope that soon I understand F# well enough to understand your answer. :-) Thanks for taking the time to answer my question. – Onorio Catenacci Oct 23 '09 at 01:29
  • You could use a sequence expression to do essentially the same thing, without requiring you to create a new builder. – Dax Fohl Jan 31 '14 at 01:38
  • Does sequence do returns? I thought it only has yield. – Pavel Minaev Jan 31 '14 at 03:23
  • `yield` can mimic `return`; see my answer all the way at the bottom. Though it occurred to me now that FSharpx has a workflow `Choice` aka `choose` that would probably be the best choice (no pun intended) for this scenario. – Dax Fohl Apr 24 '14 at 18:26
3

An option similar to Pavel's, but without needing your own workflow builder, is just to put your code block within a seq expression, and have it yield error messages. Then right after the expression, you just call FirstOrDefault to get the first error message (or null).

Since a sequence expression evaluates lazily, that means it'll only proceed to the point of the first error (assuming you never call anything but FirstOrDefault on the sequence). And if there's no error then it simply runs through to the end. So if you do it this way you'll be able to think of yield just like an early return.

let x = 3.
let y = 0.

let errs = seq {
  if x = 0. then yield "X is Zero"
  printfn "inv x=%f" (1./x)
  if y = 0. then yield "Y is Zero"
  printfn "inv y=%f" (1./y)
  let diff = x - y
  if diff = 0. then yield "Y equals X"
  printfn "inv diff=%f" (1./diff)
}

let firstErr = System.Linq.Enumerable.FirstOrDefault errs

if firstErr = null then
  printfn "All Checks Passed"
else
  printfn "Error %s" firstErr
Dax Fohl
  • 10,654
  • 6
  • 46
  • 90
1

This recursive Fibonacci function has two exit points:

let rec fib n =
  if n < 2 then 1 else fib (n-2) + fib(n-1);;
                ^      ^
Dax Fohl
  • 10,654
  • 6
  • 46
  • 90
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
  • That is an interesting point, Robert. I'm not quite sure how to translate that the question I've asked but it is a good point. – Onorio Catenacci Oct 22 '09 at 22:03
  • I edited my answer to show the exit points. The leftmost exit point is the exceptional case; it provides a path out of the recursion. – Robert Harvey Oct 22 '09 at 22:48
0

I discovered the following approach for myself. Basically, it generates a seq of possible exits, where each exit is generated by yield, then takes Seq.head of the seq to return only the exit value that is calculated first. I am not sure can we call this approach "functional" to any extent. As well not sure how efficient is this approach and how the language laziness is used under the hood. Also not sure if it is of any need for the author after so many years. But some advantages of this way is that the code looks pretty much like it looked originally in non-functional language while using only a minimal set of intrinsic features only.

See below how would look the code of the first sample of the question:

let BadParam : int = -1
let Success : int = 0

let MyFunc param =
    seq {
        if (param < 0) then
           printfn " bad param detected "
           yield BadParam

        // normal processing
        printfn "normal processing"
        yield Success
     } |> Seq.head

Let's make some test calls:

printfn "%i" (MyFunc 11)
printfn "%i" (MyFunc -11)

Let's review the output:

normal processing
0
bad param detected
-1

Hope this idea is helpful for those who may got stuck with this. Any comment regarding my concerns above are welcome for this answer.

moudrick
  • 2,148
  • 1
  • 22
  • 34
0

I like that in no way did any mentioned the F# type Result??

it is formally given by

[<Struct>]
type Result<'success,'error> =
   | Ok of 'success
   | Error of 'error

This will fit your needs and are used when handling none-IO errors for performance boost.

a good example is a parser function

type Parser<'token, 'ret,'error> = Parser of ('token seq -> Result<'ret * 'token seq, 'error>)

let Run (Parser p) = p

let OrParser parser1 parser2 =
    fun input ->
        match Run parser1 input with
        | Ok (item, rest) -> Ok(item, rest)
        | Error _ ->
            match Run parser2 input with
            | Ok (item, rest) -> Ok(item, rest)
            | Error msg -> Error msg
    |> Parser

if you are not familiar with the |> operator it simple passe the result of the left side(or above) code to the argument on the right side, here the lambda function above

The implementation of the base case function are of the users choice as long as it conform to the function type. As you might notice is that it is a highly generic function representation of any map (function) which might can fail. This includes all functions that never fails.

The above code are some of my personal (none code generation) lexer/parser generator, only used to construct the DFA in the lexer and the parser. The code snip are not invented by me

kam
  • 590
  • 2
  • 11