7
\$\begingroup\$

I am working on understanding Railway Oriented Programming (Scott Wlaschin style) in F#. In my example I want to create a pipeline which does some calculation, applies two different functions to the output of that initial calculation, then hands off a tuple of its results to a final function.

In real code the 'initial calculation' might be getting some data, the 'two different functions' might produce a textual summary and a chart of the data, and the final function might be rendering some output containing both the text and the chart.

I've invented a bind2 function to achieve this, but I can't help thinking that if this was a good idea it would already be common practice and must appear (in disguise) in existing libraries such as Scott's.

Please let me know how I would achieve what I want in an idiomatic style.

Here's my code. All above bind2 is taken broadly from Scott's code, the remainder is my bind2 function and a simple demo.

type Result<'S, 'F> =
| Success of 'S
| Failure of message : 'F list

let bind f x = 
    match x with 
    | Success s -> f s
    | Failure f -> Failure f

let bind2 (f1 : 'A -> Result<'B, 'F>)
          (f2 : 'A -> Result<'C, 'F>) (x : Result<'A, 'F>) : Result<'B * 'C, 'F> =
    match x with
    | Success _ ->
        let r1, r2 = (bind f1) x, (bind f2) x
        match r1, r2 with
        | Success s1, Success s2 ->
            Success(s1, s2)
        | Failure f1, _ ->
            Failure f1
        | _, Failure f2 ->
            Failure f2
    | Failure f -> Failure f

let demo1 (a: int) : Result<int, string> =
    Success a

let demo2 (a: int) : Result<string, string> =
    a |> string |> Success

let demo3 (a: int) : Result<string, string> =
    a |> (*) -1 |> string |> Success

let demo4 (s1 : string, s2 : string) =
    sprintf "%s - %s" s1 s2 |> Success

let Demo =
    bind demo1
    >> bind2 demo2 demo3
    >> bind demo4
\$\endgroup\$
8
\$\begingroup\$

I actually wouldn't bother defining a bind2 as this can lead to an explosion, bind3, 4, 5, 6. Instead I would define a simple computation builder to do this. So using your code from above.

 type DemoBuilder() = 
     member x.Bind(m,f) = bind f m
     member x.Return(s) = Success s

 let demo = DemoBuilder()

which you can then use like so

 let DemoB x = 
    demo {
        let! input = x
        let! a = demo1 input
        let! b = demo2 a
        let! c = demo3 a
        return (b,c)
    }

 DemoB (Success 1)
\$\endgroup\$
  • \$\begingroup\$ I was beginning to suspect that might be the endgame of all this. Thanks! \$\endgroup\$ – Kit Sep 22 '16 at 9:32
5
\$\begingroup\$

If you are chaining a series of results and combining them in various ways, then creating a computation expression (Colin's answer) is definitely the best approach.

For fun, let's look at two other approaches :)

First, it's useful to realize that your bind2 is doing too many things at once.

You can break it into two parts: combining two results to get a tuple, and doing the bind. That leads to defining a simpler function: combine:

type Result<'S, 'F> = ...

let bind f x = ...

let combine (xR : Result<'B, 'F>)
            (yR : Result<'C, 'F>) 
                : Result<'B * 'C, 'F> = 
    match xR,yR with 
    | Success x, Success y -> Success (x,y)
    | Failure e, Success y -> Failure e
    | Success x, Failure e -> Failure e
    | Failure e1, Failure e2 -> Failure e1 // or Failure (e1 @ e2) if lists

Your bind2 can now be defined using bind and combine

let bind2 (f1 : 'A -> Result<'B, 'F>)
          (f2 : 'A -> Result<'C, 'F>) 
          (x  : Result<'A, 'F>) 
              : Result<'B * 'C, 'F> =
    let r1, r2 = (bind f1) x, (bind f2) x
    combine r1 r2 

But it's not really needed, as you can normally just do it inline:

let demo1 = ...
let demo2 = ...
let demo3 = ...
let demo4 = ...

let Demo2 x =
    let y = (bind demo1) x
    let r1 = (bind demo2) y
    let r2 = (bind demo2) y 
    combine r1 r2 

All those binds are ugly, which is why a computation expression is a much nicer syntax for the same thing!

combine is an example of applicative style, which I discuss in more detail here. You can think of the Result type as an "elevated world", and combine is adding two values in that world.

Note that in this approach ("applicative style") you are getting the results from both functions and combining them. That's great for things like validation, but if you want to avoid calling the second function if the first function fails ("monadic style") then the computation expression is the way to go.

Kleisli composition

Finally, if you are working only with the functions of the form 'A -> Result<'B, 'F>, then you can also use another approach, which is Kleisli composition. In "Kleisli world" all the values are functions of the form 'A -> Result<'B, 'F> and you have various tools to combine them.

Here's the Kleisli version of bind and combine:

// compose two Kliesli functions
let composeK (f1 : 'A -> Result<'B, 'F>)
             (f2 : 'B -> Result<'C, 'F>) 
                 : 'A -> Result<'C, 'F> =
    f1 >> bind f2

// common symbol for Kliesli composition
let (>=>) f1 f2 = composeK f1 f2

// Kliesli combine
let combineK (f1 : 'A -> Result<'B, 'F>)
             (f2 : 'A -> Result<'C, 'F>) 
                 : 'A -> Result<'B * 'C, 'F> =
    fun a -> 
        let r1 = f1 a
        let r2 = f2 a
        combine r1 r2

And with them, you can write the Demo function as:

let Demo3  =
    demo1 >=> combineK demo2 demo3

Personally, I would stay away from this though, as it makes my head hurt trying to understand what's going on!

\$\endgroup\$
  • \$\begingroup\$ But Colin's answer is the correct one! You should choose that :) \$\endgroup\$ – Grundoon Sep 22 '16 at 10:20
3
\$\begingroup\$

Just a note: you should not evaluate (bind f2) until result of (bind f1) is known.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.