3
votes

I've made a simple Computation Expression workflow for dealing with Results.

[<RequireQualifiedAccess>]
module Result =

    type Builder() =

        member __.Bind(x, f) = x |> Result.bind f

        member __.Return(x) = x

        member __.ReturnFrom(x) = Ok x

    let workflow = Builder()

I also use different types to represent different kids of errors:

type ValidationError<'a> = { Obj:'a; Message:string }
type InvalidOperationError = { Operation:string; Message:string }

The problem arises when two results have different error types.

LetterString.create : string -> Result<LetterString, ValidationError<string>>
Username.create : string -> Result<Username, ValidationError<string>>
PositiveDecimal.create : decimal -> Result<PositiveDecimal, ValidationError<decimal>>
let user = 
    Result.workflow {
        let! name = LetterString.create "Tom"
        let! username = Username.create "Tom01098"
        // Error occurs here.
        let! balance = PositiveDecimal.create 100m

        return! { 
            // User record creation elided.
        }
    }
FS0001  Type mismatch. Expecting a
    'Result<PositiveDecimal,ValidationError<string>>'    
but given a
    'Result<PositiveDecimal,ValidationError<decimal>>'

I have already tried using a DU type of all errors:

type Error<'a> =
    | ValidationError of Obj:'a * Message:string
    | InvalidOperationError of Operation:string * Message:string

This has a similar problem when the generic parameter 'a is different between errors. It also loses the exact type of error in the type signature of the function.

The expected result is that the entire workflow has a unified error type, preferably as specific as possible in terms of type.

1
Could you add the rest of your code to the question? Where does the error occur? - Lee
I've beefed up the code example. The problem is that because let! is a call to Result.bind behind the scenes it does not work as the error types are different. - Tom01098
I think the relevant question here is: why do you need this 'a value? What does your error handling code do with it? Depending on the answer, you might get away with storing it as obj, or not at all. - Tarmil
Make your validation error the same in all cases. Usually this is just a string, i.e., the creation functions (which really know best what went wrong, after all) will format a string containing the error message, ready to show the user and/or log. - Jim Foye
I suppose this is best - the reasoning for the 'a is for formatting the string later, but that hardly makes any sense when I could just format it right away! My main focus for this was to make it apparent in the function signature what the error could be caused by, but I guess a single DU will have to do :( Thank you both! - Tom01098

1 Answers

2
votes

Can be solved by removing the generic parameter and using a single Error DU. Unfortunately this loses the signature I wanted, but it will have to do.