14

I've been aware of the F# Choice type for a while , but can't think of any place I'd use it rather than defining my own union type with meaningful named cases.

The MSDN documentation doesn't offer much advice ("Helper types for active patterns with two choices.") and doesn't have any example usages.

I must be missing something - what are the key advantages of this type over a custom union?

Akash
  • 2,311
  • 1
  • 20
  • 37
  • 1
    https://github.com/fsharp/fsharpx/blob/master/tests/FSharpx.Tests/ChoiceTests.fs https://github.com/fsharp/fsharpx/blob/master/tests/FSharpx.Tests/ValidationTests.fs https://github.com/fsharp/fsharpx/blob/master/tests/FSharpx.Tests/ValidationExample.fs – Mauricio Scheffer May 23 '13 at 14:19
  • 2
    Which is similar to Haskell's Either http://hackage.haskell.org/packages/archive/base/latest/doc/html/Data-Either.html – Mauricio Scheffer May 23 '13 at 14:48

4 Answers4

9

In my opinion the use cases for the Choice type are quite similar to the use cases for tuple types. In either case, you will often want to define your own more specific type which is isomorphic to the type (a custom DU for choices; a custom record type for tuples). Nonetheless, over limited scopes or in very generic situations (where good naming may become difficult), it's nice to have anonymous variants.

kvb
  • 54,864
  • 2
  • 91
  • 133
  • 1
    Thanks, I hadn't considered the similarity to tuples, especially with regard to naming in generic situations. – Akash May 23 '13 at 16:10
  • 1
    However, with tuples I almost never have to access the elements via fst or snd because I can pattern match directly into more meaningful named values. It is slightly more clunky with a Choice. – Akash May 23 '13 at 16:11
4

Sure, a more specific union type might be nice for a particular situation, but having a general Choice union means that your code can mesh well with other code when using general constructs such as Workflows, Functors, etc.

IIRC there's not an implementation of the Either Monad (Workflow in F# lingo) in the standard FSharp Core library, but there is one in the FSharpx library (though I couldn't find a constructor for it so I had to roll my own thanks to @MauricioScheffer for poiting me to choose).

From my limited, mostly C# interop, F# experience, Choice and Option aren't baked into F#'s standard methods as much as Haskell's Maybe and Either algebraic data types are baked into its standard libraries, so you don't get as much of a "this is useful" sense when using them in F# as you might in Haskell, but they are quite useful.

As for an example: in an application I recently wrote I returned Choice1Of2 from methods when I had a successful result and Choice2Of2 with an error message when something went wrong -- whether an exception being caught or a precondition not being met -- and ran my code in a Workflow for flow control. This is one standard use of this union type.

paul
  • 1,655
  • 11
  • 23
  • 1
    See https://github.com/fsharp/fsharpx/blob/master/tests/FSharpx.Tests/ChoiceTests.fs#L69 – Mauricio Scheffer May 23 '13 at 17:03
  • 1
    @MauricioScheffer ah, it's `choose`! Thanks! – paul May 23 '13 at 17:14
  • [ExtCore](https://nuget.org/packages/ExtCore/) also contains an implementation of the "Either" workflow (called `choice`) and loads of utility functions for working with Choice values: https://github.com/jack-pappas/ExtCore – Jack P. May 23 '13 at 19:12
  • @MauricioScheffer ExtCore is **not** a fork of FSharpx -- it's mostly my own code (the rest is code I've adapted from other functional languages), and it's something I started working on before FSharpx was available. If you want to collaborate, DM me on twitter or start a new thread on fsharp-opensource and we can discuss it. – Jack P. May 23 '13 at 19:58
  • @JackP. Thanks for letting me know about that, it's always nice to learn of a lib's existence – paul May 23 '13 at 20:16
  • @JackP. Sorry, didn't mean "fork" that way. I just think it would be nice to pool our efforts into a single base library, it would make the most out of the small F# community. – Mauricio Scheffer May 23 '13 at 22:05
  • Thanks Paul, very thorough answer. In the success/failure example you gave, I would use a Result union with Success and Failure cases as described in http://lucabolognese.wordpress.com/2012/11/23/exceptions-vs-return-values-to-represent-errors-in-f-iithe-critical-monad/. Maybe it just comes down to personal preference? – Akash May 24 '13 at 05:14
  • 3
    @Akash I would have liked using a Result union with Success/Failure members, as that would have been more descriptive for the purpose, but then I would have had to (re)write the Workflow as well :/ – paul May 24 '13 at 13:58
3

Best example is Async.Catch where you either return a result or an exception, both of which are meaningful.

Having said that, the use of Choice is relatively limited in F# code and most of the time people use a DU. However, a Choice might be used when you can't be bothered to define a DU.

Choice may also have better behavior when interacting with C#

John Palmer
  • 25,356
  • 3
  • 48
  • 67
  • Minor correction: it's [`Async.Catch`](http://msdn.microsoft.com/en-us/library/ee353899.aspx), not `Async.Try`. – Jack P. May 23 '13 at 12:02
2

I have observed it's value when attempting to enforce the creation of a discriminated union value via a dedicated function and Active Patterns.

Here's the example that was posted:

module File1 =

    type EmailAddress = 
        private
        | Valid   of string 
        | Invalid of string

    let createEmailAddress (address:System.String) =
        if address.Length > 0
        then Valid    address 
        else Invalid  address

    // Exposed patterns go here
    let (|Valid|Invalid|) (input : EmailAddress) : Choice<string, string>  = 
        match input with
        |Valid str -> Valid str
        |Invalid str -> Valid str

module File2 =

    open File1

    let validEmail = Valid "" // Compiler error

    let isValid = createEmailAddress "" // works

    let result = // also works
        match isValid with
        | Valid x -> true
        | _       -> false
Community
  • 1
  • 1
Scott Nimrod
  • 11,206
  • 11
  • 54
  • 118