Monads are perfect for this!
If instead of defining your own type you use the F # option, the Option.bind function already exists for this function. For reference, here is the implementation:
module Option = let bind mf = match m with | Some(x) -> fx | None -> None
You can use it like this:
let stringProcessor str = str |> (modifyFn >> checkFn) |> Option.bind (modifyFn' >> checkFn) |> Option.bind (modifyFn'' >> checkFn'')
This is good, but believe it or not, there is a way to really get rid of a little residual repeatability using computational expressions (also called do-notation in Haskell). First, take a look at the final code very quickly:
type OptionBuilder() = member this.Bind (x, f) = Option.bind fx member this.Return x = Some(x) member this.ReturnFrom x = x let option = new OptionBuilder() let stringProcessor x = option { let! x' = x |> modifyFn |> checkFn let! x'' = x' |> modifyFn' |> checkFn' return! x'' |> modifyFn'' |> checkFn'' }
To understand this, start from a different angle. Note that in functional languages, name binding is actually just masked by the application. Do not believe me? Well, this snippet:
let x = fab doSomething x doSomethingElse (x + 1)
can be considered as syntactic sugar for:
(fun x -> doSomething x doSomethingElse (x + 1)) (fab)
And this:
let a = 1 let b = 2 a * b
is interchangeable:
(fun a -> (fun b -> a * b) 2) 1
Or, more generally, let var = value in expr (where expr is all the code that comes after the simple let binding type) is interchangeable with (fun var -> expr) value .
This is how let! and Bind . We can accept this binding rule that we just saw and study how it works with calculations. This form:
comp { let! var = value in expr }
equivalently
comp.Bind (value, (fun var -> comp { expr }))
where we apply the same rule to expr . In addition, there are many other forms, such as return , which you can find here here . Now, as an example, try undoing this:
comp { let! x' = fx let! x'' = f (x' + 1) return (gx) }
For our first step, choose the first let! and take it off. This gives us:
comp.Bind (fx, (fun x' -> comp { let! x'' = f (x' + 1) return (gx) }
Doing this again allows us to:
comp.Bind (fx, fun x' -> comp.Bind (f x', (fun x'' -> comp { return (g x'') }))
Which will finally become:
comp.Bind (fx, fun x' -> comp.Bind (f x', (fun x'' -> comp.Return (g x''))