Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to combine Option and Result

Tags:

f#

I find myself returning type Option<Result<'a,'b>> a lot, my reasoning is the following:

I use Option to decide if I'm interested in processing an event, and if I do, then I return a result of processing it.

Is there a proper name for this pattern? And better operators? For example, I use: Option.map(Result.map f) is there an operator for mapping nested functors?

like image 239
Coding Edgar Avatar asked Oct 02 '20 19:10

Coding Edgar


2 Answers

I think the existing answer from Gus answers your question about the name of the pattern. In Haskell, this is achieved using monad transformers and the F#+ library lets you do this in F# too.

However, this pattern is not very common in most F# code. One reason is that it leads to very complicated code (both the types get complex and the processing code gets pretty cumbersome).

If you find yourself using Option<Result<'T>> often, then I would consider if it makes sense to define a new type for this, perhaps using names that makes sense in your domain, so that the reader of the code can easily understand it. You could use something like:

type ProcessingResult<'T> = 
  | Ignored
  | Failed of string
  | Accepted of 'T

You will lose built-in functions for Option and Result, but you can implement the ones you need yourself. It is not too complicated:

module Processing =
  let map f = function
    | Ignored -> Ignored
    | Failed s -> Failed s
    | Accepted v -> Accepted (f v)

The benefit of this is that Processing.map is much easier to read, which I think is a trade-off worth making.

like image 138
Tomas Petricek Avatar answered Sep 20 '22 14:09

Tomas Petricek


I would call it a monadic stack.

You can define your own operators, but if you want to map them, you need something like a "deep map level 2" which normally is (map >> map) so, in this case:

Some (Result<int,string>.Ok 1) |> (Result.map >> Option.map) ((+) 1)
// val it : Result<int,string> option = Some (Ok 2)

If you want to explore more F#+ provides limited support for this through 2 different abstractions:

  • Composed functors: by using the Compose type from FSharpPlus.Data:
let v1 = Compose (Some (Result<int,string>.Ok 1)) 
let v2 = v1 |> map ((+) 1)
// .. more operations, then when you're finished ..
let v3 = Compose.run v2

// val v1 : Compose<Result<int,string> option> = Compose (Some (Ok 1))
// val v2 : Compose<Result<int,string> option> = Compose (Some (Ok 2))
// val v3 : Result<int,string> option = Some (Ok 2)

So it allows you to compose arbitrary Functors, which are types you can map on.

  • Monad transformers: by using in this particular case the ResultT type from FSharpPlus.Data:

The previous example would work, just replace Compose for ResultT, but now you can also do monadic operations:

let x = monad' {
    let! v1 = ResultT (Some (Result<int,string>.Ok 1))
    let! v2 = ResultT (Some (Result<int,string>.Ok 1))
    return v1 + v2 }
ResultT.run x

// val x : ResultT<Result<int,string> option> = ResultT (Some (Ok 2))
// val it : Result<int,string> option = Some (Ok 2)
like image 42
Gus Avatar answered Sep 23 '22 14:09

Gus