Seiten

Mittwoch, 4. Juli 2012

F# Question. FSharpx Choice foldM.

open System
open System.Collections
open FSharpx
open FSharpx.Choice

let foo = ["A";"B";"C"]

let inputs1 = ["A";"D";"b"]
let inputs2 = ["A";"A";"C"]

let listMonoid  = new Monoid.ListMonoid<_>()
let inline tryFind x list name = fromOption [sprintf " %A not found in %A " x name] (List.tryFind ((=) x) list)
let tryFindFoo x = tryFind x foo "foo"

let inline testFoldM input = 
    foldM 
        (fun acc s -> 
            Validation.apm 
                        listMonoid 
                        (returnM acc) 
                        (tryFindFoo s |> map List.cons)) 
        [] 
        input
    |> choice List.rev id

let inline testFold input = 
    List.foldBack 
        (fun s acc  -> 
            Validation.apm 
                        listMonoid 
                        acc
                        (tryFindFoo s |> map List.cons)) 
        input
        (returnM [] )
    |> choice id id

printfn "test foldM : %A" (List.map testFoldM [inputs1; inputs2]) 
printfn "test fold : %A" (List.map testFold [inputs1; inputs2]) 
// Why foldM stops after the first appearance on the Choice2Of2 case ?

// I would expect that testFoldM behaves exactly like testFold.

test foldM : [[" "D" not found in "foo" "]; ["A"; "A"; "C"]]
test fold : [[" "D" not found in "foo" "; " "b" not found in "foo" "]; ["A"; "A"; "C"]]

UPDATE: The solution from Mauricio Scheffer
"If you take a look at its definition, you'll see that Choice.foldM is defined in terms of monadic return and bind. OTOH Validation, even though it uses the same underlying type Choice1Of2 | Choice2Of2, doesn't have a proper monadic instance, it's instead just an applicative functor. The Either monad (called Choice in FSharpx) does "short-circuit" evaluation on bind, just like the Maybe monad. But for validation, you usually want to accumulate errors instead, so you want the opposite of this short-circuit evaluation, so you don't want anything that uses Choice.bind, therefore you don't want Choice.foldM. What you can use in this case is Validation.mapM, which is built on top of Validation.sequence, which in turn is built on the applicative functor. (Now that I think about it, mapM isn't such a good name, since it's not really monadic! I wonder how a mapM-like function is defined in Haskell over sequenceA). This function is equivalent to your testFold function:

let testMapM = Validation.mapM tryFindFoo |> choice id id

Well, at least it gives the same output in this test BTW I'd only open FSharpx.Choice if you're going to use the operators... otherwise I'd prefer to just open FSharpx and then explicitly call Choice.foldM, etc. Yes, Choice.choice looks a bit ridiculous, not sure how to name it "
let solution input = Validation.mapM tryFindFoo input |> choice id id
printfn "Solution: %A" (List.map solution [inputs1; inputs2])

4 Kommentare:

  1. Answered on G+ https://plus.google.com/114990891314287747045/posts/B2SjhfdvmN1

    AntwortenLöschen
  2. (copied from G+)

    If you take a look at its definition, you'll see that Choice.foldM is defined in terms of monadic return and bind. OTOH Validation, even though it uses the same underlying type Choice1Of2 | Choice2Of2, doesn't have a proper monadic instance, it's instead just an applicative functor. The Either monad (called Choice in FSharpx) does "short-circuit" evaluation on bind, just like the Maybe monad. But for validation, you usually want to accumulate errors instead, so you want the opposite of this short-circuit evaluation, so you don't want anything that uses Choice.bind, therefore you don't want Choice.foldM.

    What you can use in this case is Validation.mapM, which is built on top of Validation.sequence, which in turn is built on the applicative functor. (Now that I think about it, mapM isn't such a good name, since it's not really monadic! It's a limited form of Data.Traversable.traverse... or maybe more precisely Data.Foldable.concatMap).
    This function is equivalent to your testFold function:

    let testMapM = Validation.mapM tryFindFoo >> choice id id

    Well, at least it gives the same output in this test :)
    BTW I'd only open FSharpx.Choice if you're going to use the operators... otherwise I'd prefer to just open FSharpx and then explicitly call Choice.foldM, etc. Yes, Choice.choice looks a bit ridiculous, not sure how to name it :)

    AntwortenLöschen
  3. Then you asked about the difference between the monad and the applicative functor, or rather, how can I know when is a monad and when is a applicative functor to use.

    For example, look up in two lists with the following validation:
    "not found in the first list", "not found in the second" or "not found in both", otherwise the input list.

    Here are a few alternatives: https://gist.github.com/3056995
    I'd keep it simple and use pattern matching. I don't think you can use the either monad in this case because you need *both* validation results in order to determine if you output "not found in both" and you can't do that with short-circuit evaluation.

    AntwortenLöschen
  4. There's a typo in testMapM, it's

    let testMapM = Validation.mapM tryFindFoo >> choice id id

    not

    let testMapM = Validation.mapM tryFindFoo |> choice id id

    AntwortenLöschen