1

I'm trying to write a computational expression that effectively threads warnings and errors from all dependent let! arguments and combines them with a result of a given calculation. The problem is getting the error list from all used let! to concatenate them after the result of the calculation defined. To illustrate:

Given the type

type ValueWithErrors<'value> = 
| Value of 'value * string list
| Undefined of string list

and a crude bind function for this stage:

let public bind calculation = function
| Value(value, errors) -> 
    try
        let result = calculation(value)
        match result with
        | Value(resultValue, resultErrors) -> Value(resultValue, (errors |> List.append resultErrors))
        | Undefined(resultErrors) -> Undefined(errors |> List.append resultErrors)
    with
        | _ as ex -> Undefined(ex.Message :: errors)
| Undefined(errors) -> Undefined(errors)

I want to write a computation function that allows me to do something like:

calc {
    let! value1 = ValueWithErrors (5, ["ERROR"])
    let! value2 = ValueWithErrors (6, ["ERROR2"])
    return (value1 + value2)
    }   

with the result being Value(11, ["ERROR", "ERROR2"])

So I've defined an incomplete builder as thus (note the Return() impl missing)

type public ValueWithErrorsBuilder() = 
    member this.Bind(m, f) = 
        bind f m
    member this.ReturnFrom(value) = 
        value
    member this.Return(value) =
        Value(value, []) // This is wrong, don't have access to previous binding 

In C# I can do this pretty easily with a custom SelectMany(source, calc, resultSelector) extension method on my custom type and the LINQ query syntax carries through the errors. Like so:

from value1 in ValueWithErrors (5, ["ERROR"])
from value2 in ValueWithErrors (6, ["ERROR2"])
select (value1 + value2)

I would like to do the same in F# - is this possible at all?

Cœur
  • 37,241
  • 25
  • 195
  • 267
akara
  • 396
  • 2
  • 7
  • 2
    why is your return implementatin *wrong* - it looks just right to me. What do you mean if you say "don't have access to previous bindings"? - btw you are just trying to implement the writer-monad: https://www.haskell.org/haskellwiki/All_About_Monads#The_Writer_monad and have a look here: https://github.com/fsprojects/fsharpx/blob/7246e314e3fdeae07f3465b9126f2bc22faa0cd5/src/FSharpx.Core/ComputationExpressions/Monad.fs for a complete implementation in F# – Random Dev Nov 03 '14 at 05:52
  • 1
    maybe you missunderstood how the computation is translated into the parts of the builder! - Have you tried the code as is? (btw: your code would bet translated into something like `builder.Bind(ValueWithErrors(5,["ERROR"]), (fun value1 -> builder.Bind(ValueWithErrors (6, ["ERROR2"]), (fun value2 -> builder.Return(value1+value2)))))` and if you follow your bind-implementation you will see that you first compute `value1+value2` before you append the *logs* - so you don't need access in return to the last values. BTW: **return** is really named poorly in this case. – Random Dev Nov 03 '14 at 05:56
  • Your right - I didn't need to propogate the logs - the following Bind's do it for me. The other question I have though which may be equally obvious is if that builder.Return(value1 + value2) throws an exception I would like to capture that in my return. Can I override how builder.Return gets its argument? (i.e rather than in this case a number, getting a function () -> 'value that I can invoke and catch exceptions from)? – akara Nov 03 '14 at 06:21
  • To answer my own question the Bind method captures the exception and allows me to handle the exception case - in this case with an Undefined union. – akara Nov 03 '14 at 06:35

0 Answers0