I`m pretty new to F# world and currently trying to experiment with functions. Trying to find new techniques to make them more compact and flexible and move from C#-like coding style. So I have a pretty ugly code of mine with constant checking and pattern matching:
type StringChecking =
| Success of string
| Fail
let StringProcessor(str : string) =
let newstr = modifyFn str
let result = checkFn newstr
match result with
| true -> Success result
| false ->
let newstr' = modifyFn' str
let result' = checkFn newstr'
match result' with
| true -> Success newstr'
| false ->
let newstr'' = modifyFn'' str
let result'' = checkFn newstr''
match result'' with
| true -> Success newstr''
| false ->
Fail
Are there some useful techniques that can help me to eliminate all these stages and make my code more elegant. Sorry if the question seems awkward or stupid, but I really want to do something with this one, because I want to use it in some project of mine. I`ve grown pretty tired of constant checks in C#. Thank you for your time and suggestions.
This is assuming the objective is transforming an object repeatedly in a chain of steps when only on successful completion of the previous the next step will be performed. This will be encapsulated by F#'s Option
type here.
If you are after conciseness, you could do worse than with some kind of monadic bind.
let (>>=) arg f = // bind operator
match arg with
| Some x -> f x
| None -> None
let checkWith checker x =
if checker x then Some x else None
"sample String"
|> (modifyFn >> checkWith checkFn)
>>= (modifyFn' >> checkWith checkFn')
>>= (modifyFn'' >> checkWith checkFn'')
But you do notice the awkward first step in the pipelining. which results from the unwrapped value at the beginning of the computation. Thus:
Success "sample String"
>>= (modifyFn >> checkWith checkFn)
>>= (modifyFn' >> checkWith checkFn')
>>= (modifyFn'' >> checkWith checkFn'')
In case you wanted to modify and validate the same original value instead of the result of the previous step, let's simply ignore it.
let str ="sample String"
modifyFn str |> checkWith checkFn
>>= (fun _ -> modifyFn' str |> checkWith checkFn')
>>= (fun _ -> modifyFn'' str |> checkWith checkFn'')
In the case that the computation terminates with the successful completion of a step, otherwise to continue with alternative steps, we will have different signatures. Akin to F#'s defaultArg
function (arg:'T option -> defaultvalue:'T > 'T
), I'll propose a bind-like defaultBy
:
let defaultBy c a f =
match a with
| None -> f c
| x -> x
let (>?=) f = defaultBy "sample string" f
"sample string"
|> (modifyFn >> checkWith checkFn)
>?= (modifyFn' >> checkWith checkFn')
>?= (modifyFn'' >> checkWith checkFn'')
Monads are perfect for this!
If instead of defining your own type you use F#'s option, there's already a function exactly for this as Option.bind
. For reference, here's the implementation:
module Option =
let bind m f =
match m with
| Some(x) -> f x
| None -> None
You can use it like so:
let stringProcessor str =
str
|> (modifyFn >> checkFn)
|> Option.bind (modifyFn' >> checkFn)
|> Option.bind (modifyFn'' >> checkFn'')
This is nice, but believe it or not, there's a way to actually get rid of the little remaining repetitiveness using computational expressions (also known as do-notation in Haskell). First, let's look at the final code real quick:
type OptionBuilder() =
member this.Bind (x, f) = Option.bind f x
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, let's start from a different angle. Consider that in functional languages, name binding is actually just function application in disguise. Don't believe me? Well, this snippet:
let x = f a b
doSomething x
doSomethingElse (x + 1)
can be thought of syntactic sugar for:
(fun x ->
doSomething x
doSomethingElse (x + 1))
(f a b)
And this:
let a = 1
let b = 2
a * b
is interchangeable with this:
(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 kind of let binding) is interchangeable with (fun var -> expr) value
.
This is how a computation expression's let!
and Bind
are related. We can take that let binding rule we just saw and examine how it works with computation expressions. This form:
comp { let! var = value in expr }
is equivalent to
comp.Bind (value, (fun var -> comp { expr }))
where we apply the same rule to expr
. Additionally, there are many other forms defined, such as return
, which you can find here here. Now, as an example, let's try to desugar this:
comp {
let! x' = f x
let! x'' = f (x' + 1)
return (g x) }
For our first step, pick off the first let!
and desugar it. That give us:
comp.Bind (f x, (fun x' ->
comp {
let! x'' = f (x' + 1)
return (g x) }
Doing this again gets us to:
comp.Bind (f x, fun x' ->
comp.Bind (f x', (fun x'' ->
comp { return (g x'') }))
Which will finally become:
comp.Bind (f x, fun x' ->
comp.Bind (f x', (fun x'' ->
comp.Return (g x''))
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