Search code examples
f#monads

How to implement `map` using the fish (>=>, Kleisli composition) operator in F#?


I'm learning monadic composition through Scott Wlaschin's Railway-oriented Programming post. Oncebind, switch, and >=> functions are defined, he introduces map to show how to "turn a one-track function into a two-track function". That is:

f: a -> b     =>    f': T<a,c> -> T<b,c>

The implementation in the article is the following:

let map oneTrackFunction twoTrackInput =
    match twoTrackInput with
    | Success s -> Success (oneTrackFunction s)
    | Failure f -> Failure f

Did an an equivalent implementation using switch and bind as an exercise,

let map' f = bind (switch f)

but when I tried to implement map with >=>, I arrived at this ugly mess:

let map'' f result =
    match result with
        | Ok o -> ((fun _ -> result) >=> (switch f)) o
        | Error e -> Error e

Note to self: o could be any value of type 'a (if result : Result<'a,'c>), because f's input is already saved in the closure used as >=>'s first operand, but this was the only way I could think of to keep it more generic.

Is there a "cleaner" implementation similar to map's?


Notes

I used the following example to test the maps above:

map  ((+) 2) ((Ok 27) : Result<int,string>)

Used implementations of bind, switch, and >=>:

let bind
    (     f : 'a -> Result<'b,'c>)
    (result :       Result<'a,'c>)
    =
    match result with 
   |    Ok o -> f o
   | Error e -> Error e

let switch
   (f : 'a -> 'b)
   (x : 'a      )
   =
   f x |> Ok

let (>=>)
    (f : 'a -> Result<'b,'error>)
    (g : 'b -> Result<'c,'error>)
    =
    f >> (bind g)

Solution

  • Referring to German wikipedia with examples in Haskell, there are three ways to define the set of monadic operators: either with

    Since you have the Kleisli operator already and want to derive map from it, you only need return (which is Ok here) for this:

    let fmap f = id >=> (Ok << f)
    // val fmap : f:('a -> 'b) -> (Result<'a,'c> -> Result<'b,'c>)
    

    F# code for the three alternatives:

    module Result1 =
        // Definition by fmap and join
        let fmap = Result.map
        let join mma = Result.bind id mma
        // derived:
        let (>>=) ma f = (join << fmap f) ma 
        let (>=>) f g = join << fmap g << f
    
    module Result2  =
        // Definition by return and >>=
        let ``return`` = Ok
        let (>>=) ma f = Result.bind f ma
        // derived:
        let (>=>) f g a = f a >>= g
        let fmap f ma = ma >>= (``return`` << f)
        let join mma = mma >>= id
    
    module Result3  =
        // Definition by return and >=>
        let ``return`` = Ok
        let (>=>) f g a = Result.bind g (f a)
        // derived:
        let (>>=) ma f = (id >=> f) ma
        let fmap f = id >=> (``return`` << f)
        let join mma = (id >=> id) mma