Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# constant pattern matching and checks elimination

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.

like image 275
Zinodust Avatar asked Feb 24 '16 19:02

Zinodust


2 Answers

The Successful Path

enter image description here

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'')

The Alternative Path

enter image description here

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'')
like image 128
kaefer Avatar answered Nov 15 '22 09:11

kaefer


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''))
like image 26
Jwosty Avatar answered Nov 15 '22 09:11

Jwosty