Continuous F # Compliance and Verification

I am very new to the F # world and am currently trying to experiment with functions. Trying to find new methods to make them more compact and flexible and move from a coding style like C #. Therefore, I have a rather ugly code 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 any useful methods that can help me eliminate all these steps and make my code more elegant. Sorry if the question seems uncomfortable or silly, but I really want to do something with this because I want to use it in some kind of project. I am very tired of the constant checks in C #. Thanks for your time and suggestions.

+5
source share
2 answers

Successful way

enter image description here

It is assumed that the goal is to repeatedly transform the object into a chain of steps, when only with the successful completion of the previous one the next step will be completed. Type F # Option will be enclosed here.

If you are after brevity, you can do worse than with some kind of monadic bind .

 let (>>=) arg f = // bind operator match arg with | Some x -> fx | 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 noticed the awkward first step in pipelining. which is obtained from the expanded value at the beginning of the calculation. In this way:

 Success "sample String" >>= (modifyFn >> checkWith checkFn) >>= (modifyFn' >> checkWith checkFn') >>= (modifyFn'' >> checkWith checkFn'') 

If you want to change and confirm the same original value instead of the result of the previous step, just skip it.

 let str ="sample String" modifyFn str |> checkWith checkFn >>= (fun _ -> modifyFn' str |> checkWith checkFn') >>= (fun _ -> modifyFn'' str |> checkWith checkFn'') 

Alternative way

enter image description here

If the calculation completes with the successful completion of the step, otherwise we will have different signatures to continue the alternative steps. Similar to the F # function defaultArg ( arg:'T option -> defaultvalue:'T > 'T ), I will suggest a binding of type defaultBy :

 let defaultBy caf = match a with | None -> fc | x -> x let (>?=) f = defaultBy "sample string" f "sample string" |> (modifyFn >> checkWith checkFn) >?= (modifyFn' >> checkWith checkFn') >?= (modifyFn'' >> checkWith checkFn'') 
+5
source

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'')) 
+4
source

Source: https://habr.com/ru/post/1243788/


All Articles