What am I doing wrong with my Result.bind/map/mapError in this code?

I have this code which is purely for experimentation and not used for anything real.
(I realise that the code can be simplified – see below – but that’s not the point of my current exercise.)
Note: The multiply and divide functions and their corresponding Error types have been removed for clarity(?)

The output I expect to get is of Result<float, ArithmeticError>

let basicAdd addend1 addend2 = addend1 + addend2

type AdditionError =
| AdditionOverflowError
| UnknownAdditionError

let add addend1 addend2 =
    try
        Ok (basicAdd addend1 addend2)
    with
        | :? System.OverflowException -> Error AdditionOverflowError
        | _ -> Error UnknownAdditionError 

type SubtractionError =
| SubtractionOverflowError
| UnknownSubtractionError

let basicSubtract minuend subtrahend = minuend - subtrahend

let subtract minuend subtrahend =
    try
        Ok (basicSubtract minuend subtrahend)
    with
        | :? System.OverflowException -> Error SubtractionOverflowError
        | _ -> Error UnknownSubtractionError

type ArithmeticError =
| ArithmeticAdditionError of AdditionError
| ArithmeticSubtractionError of SubtractionError

let result =
    Ok 20.0
    |> Result.map (basicAdd 30.0)
    |> Result.bind (add 100.0)
    |> Result.bind (subtract 80.0)

I am getting a Type Mismatch error on the last line, because a SubtractionError is not an AdditionError, and that’s understandable.
However, I cannot find a way to get the subtraction to work.

I tried adding

|> Result.mapError ArithmeticAdditionError

…before the last line but that just changes the error into a slightly different one.

I tried exchanging the last line for

|> subtract 80.0

…but that’s no better.

I tried using

|> Result.map (subtract 80.0)

…but that gives me a Result<Result<>> which is no good.

I just can’t seem to get my head around what I’m supposed to do with this.
I thought I had a tiny grasp on this area until I came up with this problem.
Can anyone help?
Am I missing something very simple?
Is this something that I need to be using computational expressions for? (I really don’t want to get into those just yet if I can help it.)

I know the code can be simplified by re-coding as something like

let basicAdd addend1 addend2 = addend1 + addend2

let basicSubtract minuend subtrahend = minuend - subtrahend

type ArithmeticError =
| OverflowError
| UnknownError

let errorify f operand1 operand2 =
    try
        Ok (f operand1 operand2)
    with
        | :? System.OverflowException -> Error OverflowError
        | _ -> Error UnknownError 

let add = errorify basicAdd
let subtract = errorify basicSubtract

let result =
    Ok 20.0
    |> Result.map (basicAdd 30.0)
    |> Result.bind (add 100.0)
    |> Result.bind (subtract 80.0)

…and it works, but I’m trying to treat the two arithmetic operations as things from different systems that I can’t modify, as could happen in real life.

You’re on the right track with this. Whenever you’re using Result.bind the error types have to line up. So if you have a |> Result.bind f, then a’s error type has to match the return of f’s error type. Adding the Result.mapError there is half of the answer, because what you pass into the final line now has the right error type, but subtract 80.0 still has the wrong error type. I would change it to

let result = 
  let add x y = add x y |> Result.mapError ArithmeticAdditionError
  let subtract x y = subtract x y |> Result.mapError ArithmeticSubtractionError

  Ok 20.0
  |> Result.map (basicAdd 30.0)
  |> Result.bind (add 100.0)
  |> Result.bind (subtract 80.0)

or all inline:

let result = 
  Ok 20.0
  |> Result.map (basicAdd 30.0)
  |> Result.bind (add 100.0 >> Result.mapError ArithmeticAdditionError)
  |> Result.bind (subtract 80.0 >> Result.mapError ArithmeticSubtractionError)
2 Likes

Wow, that’s such a simple solution.

I knew I had to use mapError with subtract but for some reason I thought I had to do it after the bind (as I had tried earlier) which wouldn’t work.

It never occurred to me to combine the subtract and mapError and then bind the resultant function.

Now I’m feeling foolish, but also better-off by knowing that my reasoning was bad and that I’ve learned something useful.

Many thanks, again.