5

Recently I discovered a style of programming which is very useful (and pretty) in functional world called Railway oriented programming. For example when want to create a pipeline of functions that produce option type, and we want to return None if any of them fails we can do something like this:

someOptionValue // : 'a option
>>= func1       // func1: 'a -> 'b option
>>= func2       // func2: 'b -> 'c option
and so on...

where (>>=) : 'a option -> (a' -> 'b option) -> 'b option operator applies a value to the left hands side if it's Some value or None otherwise.

But, here is my problem, what if we have a function that "takes" two (or many) option types, let say funcAB : 'a -> 'b -> 'c option, valA : 'a option and valB : 'b option and we still want to create this pipeline or use some nice operator (not create a new one specifically for this, but use some standard approach, in particular I don't want to use match ... with to "unpack" option values)

Currently I have something like this:

valA
>>= (funcAB >> Some)
>>= (fun ctr -> valB >>= ctr)

But is doesn't seem 'correct' (or fun is the better word ;] ), and it doesn't scale well if a function takes more parameters or we want to create a longer pipeline. Is there a better way to do this?

I've used F# syntax but I think this question can be applied to any functional programming language, like OCaml and Haskell.

EDIT (Solution):

Thanks to the chi's answer I've created following code F# which is much more idiomatic then what I previously had:

funcAB <!> valA <*> valB |> Option.flatten

And it looks well if we have more values: funcAB <!> valA <*> valB <*> valC <*> ....

I've used operators defined in YoLo.

mateuszlewko
  • 1,110
  • 8
  • 18
  • 2
    For your types you can use basic application `funcAB valA valB`, but something tells me you didn't intend `funcAB` to take options as input (?). Can you clarify? – chi Apr 16 '17 at 16:50
  • So image that funcAB is a function from 3rd party api and it doesn't take option type, and even if it did it would need to handle option values (like a constructor that can create object only if all arguments are Some). EDIT: Yes, funcAB shouldn't take option types, I've corrected this. – mateuszlewko Apr 16 '17 at 16:53
  • 1
    Ok, I'd suggest you edit your question accordingly, fixing the input types to be non-options. – chi Apr 16 '17 at 16:54
  • 1
    FYI: There's also `Option.map2` and `Option.map3` that can help. But there's no `Option.map4` which means Applicative is the way to go in the general case IMO. In addition, in F# it's somewhat frowned upon using operators (I personally don't agree with this, just saying). Therefore a possibly more F# idiomatic way is to use `|>`: `pure funcAB |> ap valA |> ap valB` – Just another metaprogrammer Apr 17 '17 at 10:28

2 Answers2

5

In Haskell, we can use Applicative syntax for that:

If

valA :: f a
valB :: f b
funAB :: a -> b -> f c

then

join $ funAB <$> valA <*> valB :: f c

provided f is a monad (like Maybe, Haskell's option).

It should be adaptable to F# as well, I guess, as long as you define your operators

(<$>) ::   (a -> b) -> f a -> f b
(<*>) :: f (a -> b) -> f a -> f b
join  :: f (f a) -> f a

The above trick is a poor man's version of Idris !-notation (bang notation).

Another common option is using do

do a <- valA
   b <- valB
   funAB a b

but this is comparable with using >>=, indeed:

valA >>= \a ->
valB >>= \b ->
funAB a b

is not much more complex.

Community
  • 1
  • 1
chi
  • 111,837
  • 3
  • 133
  • 218
2

One option is to use computation expressions. For option there is no standard one but you can easily create your own:

type OptionBuilder() =
    member this.Bind (x, f) = Option.bind f x
    member this.Return x = Some x

let optional = OptionBuilder()

let a, b = Some(42), Some(7)
let f x y = x + y

let res = optional {
    let! x = a
    let! y = b
    return f x y
}

which closely resembles Haskells do notation. For more advanced features, have a look at F#+ which also has a generic applicative functor operator <*>.

CaringDev
  • 8,391
  • 1
  • 24
  • 43