views:

350

answers:

4

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?

+3  A: 

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
@kvb Thanks for the response. I think that you've probably hit on a very good approach.
Onorio Catenacci
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
+1  A: 

This recursive Fibonacci function has two exit points:

let rec fib n =
if n < 2 then 1 else fib (n-2) + fib(n-1);;
              ^      ^
Robert Harvey
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
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
+2  A: 

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 Sanders
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
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 Sanders
@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
+2  A: 

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
@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