Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Monadic operations on Choice<'T1,'T2>

I could not find an object choice in the standard libraries, that allows me to write

let safeDiv (numer : Choice<Exception, int>) (denom : Choice<Exception, int>) =
    choice {
        let! n = numer
        let! d = denom
        return! if d = 0
            then Choice1Of2 (new DivideByZeroException())
            else Choice2Of2 (n / d)
    }

like in Haskell. Did I miss anything, or is there a third-party library for writing this kind of things, or do I have to re-invent this wheel?

like image 631
fghzxm Avatar asked Jul 25 '18 14:07

fghzxm


2 Answers

There is no built-in computation expression for the Choice<'a,'b> type. In general, F# does not have a built-in computation expression for the commonly used Monads, but it does offer a fairly simple way to create them yourself: Computation Builders. This series is a good tutorial on how to implement them yourself. The F# library does often have a bind function defined that can be used as the basis of a Computation Builder, but it doesn't have one for the Choice type (I suspect because there are many variations of Choice).

Based on the example you provided, I suspect the F# Result<'a, 'error> type would actually be a better fit for your scenario. There's a code-review from a few months ago where a user posted an Either Computation Builder, and the accepted answer has a fairly complete implementation if you'd like to leverage it.

like image 104
Aaron M. Eshbach Avatar answered Nov 07 '22 20:11

Aaron M. Eshbach


It is worth noting that, unlike in Haskell, using exceptions is a perfectly acceptable way to handle exceptional situations in F#. The language and the runtime both have a first-class support for exceptions and there is nothing wrong about using them.

I understand that your safeDiv function is for illustration, rather than being a real-world problem, so there is no reason for showing how to write that using exceptions.

In more realistic scenarios:

  • If the exception happens only when something actually goes wrong (network failure, etc.) then I would just let the system throw an exception and handle that using try ... with at the point where you need to restart the work or notify the user.

  • If the exception represents something expected (e.g. invalid user input) then you'll probably get more readable code if you define a custom data type to represent the wrong states (rather than using Choice<'a, exn> which has no semantic meaning).

It is also worth noting that computation expressions are only useful if you need to mix your special behaviour (exception propagation) with ordinary computation. I think it's often desirable to avoid that as much as possible (because it interleaves effects with pure computations).

For example, if you were doing input validation, you could define something like:

let result = validateAll [ condition1; condition2; condition3 ]

I would prefer that over a computation expression:

let result = validate {
  do! condition1
  do! condition2
  do! condition3 }

That said, if you are absolutely certain that custom computation builder for error propagation is what you need, then Aaron's answer has all the information you need.

like image 40
Tomas Petricek Avatar answered Nov 07 '22 19:11

Tomas Petricek