I like to use the pipe operator '|>' a lot. However, when mixing functions that return 'simple' values with functions that return 'Option-Typed-values', things become a bit messy e.g.:
// foo: int -> int*int
// bar: int*int -> bool
let f (x: string) = x |> int |> foo |> bar
works, but it might throw a 'System.FormatException:...'
Now assume I want to fix that by making the function 'int' give an optional result:
let intOption x =
match System.Int32.TryParse x with
| (true, x) -> Some x
| (false,_) -> None
Only problem now is that of course the function
let g x = x |> intOption |> foo |> bar
won't compile due to typing errors. Ok, simply define an 'optionalized' pipe:
let ( |= ) x f =
match x with
| Some y -> Some (f y)
| None -> None
now I can simply define:
let f x = x |> intOption |= foo |= bar
and everything works like a charme.
OK, question: Is that idiomatic F#? Acceptable? Bad style?
Remark: Of course, given the right types the '|=' operator allows to split and merge 'pipelines' with options at will while only care about options where they matter:
x |> ...|> divisionOption |= (fun y -> y*y) |=...|>...
The pipeline operator in JavaScript uses a very specific syntax. It uses this "pipe"|> symbol. In order for this operator to be used, it must be placed between the value you wish to pass to a function call and the function you wish to call.
What is the Pipe Operator? The pipe operator is a special operational function available under the magrittr and dplyr package (basically developed under magrittr), which allows us to pass the result of one function/argument to the other one in sequence. It is generally denoted by symbol %>% in R Programming.
Title A Forward-Pipe Operator for R. Version 2.0.3. Description Provides a mechanism for chaining commands with a new forward-pipe operator, %>%. This operator will forward a value, or the result of an expression, into the next function call/expression.
I think using Option.map would be more idiomatic:
let g x = x |> intOption |> Option.map foo |> Option.map bar
There are two aspects not yet covered by other answers.
Option
typeInstead of a full-blown computation expression like MaybeBuilder()
, we can define let-bound functions providing monadic operations for the Option
type. Let's represent the bind operation by the operator >>=
:
let (>>=) ma f = Option.bind f ma
// val ( >>= ) : ma:'a option -> f:('a -> 'b option) -> 'b option
let ``return`` = Some
// val return : arg0:'a -> 'a option
From this follows
let (>=>) f g a = f a >>= g
// val ( >=> ) : f:('a -> 'b option) -> g:('b -> 'c option) -> a:'a -> 'c option
let fmap f ma = ma >>= (``return`` << f)
// val fmap : f:('a -> 'b) -> ma:'a option -> 'b option
let join mma = mma >>= id
// val join : mma:'a option option -> 'a option
fmap
is basically Opion.map
; join
un-nests a nested instance by one level, and composition by the Kleisli operator >=>
is an alternative to pipelining.
In lightweight syntax, operators are exempted from increasing indentation with nested scope. This could be useful when stringing together lambda functions, allowing nesting while still indenting by at most one level.
a_option
|> Option.bind (fun a ->
f a
|> Option.bind (fun b ->
g b
|> Option.bind ... ) )
vs
a_option
>>= fun a ->
f a
>>= fun b ->
g b
>>= ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With