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