Поделиться через


Two Track Coding (ROP) For Dummies – Part 2

Hello World!

Pull from my external blog website: https://indiedevspot.azurewebsites.net/2015/01/20/two-track-coding-rop-for-dummies-part-2/#more-4791

Welcome to Part 2!  Hopefully you made it through part 1 with no issues.  In this part, I will discuss Applicatives, in this scenario, we are doing validation of an entire type, however you can use this method for any sort of processing in the system that returns a result or failure and takes n parameters.

Part 1: Bind
Part 2: Applicatives
Part 3: Options

 

Top Down View

So lets start off by looking at the end result and break it down from there again.

1 2 3 4 5 6 7 8 9 let DoStuff () =     CreateNerd     <!> succeed 1     <*> FormatHelper.CreateFirstName "D"     <*> FormatHelper.CreateLastName ""     <*> succeed "1231231234"     <*> succeed "david@david.com"     <*> succeed 1     <*> succeed "1,2,4"

So this should be fairly straight forward, especially if you went through part 1.  We have a function, DoStuff, which takes zero parameters, and returns a Result.

  1. We have two new infix operators <!> (lift) and <*> (apply)
  2. We have a new function CreateNerd
  3. We have two new functions succeed and fail

Lifting

The most logical place to begin is with the first infix operator <!>, or Lift.

1 2 3 4 5 6 7 /// given a function that transforms a value /// apply it only if the result is on the Success branch let liftR f result =     let f' =  f |> succeed     applyR f' result   let (<!>) = liftR

Whew, an easy one!  LiftR takes a function (f) and a result.  It succeeds f and pipes it into this applyR.  So we are lifting the result into this function.  We already know the definition for the infix operators.

So what does this mean?  Well its easier to really understand this in more context.  Succeed simply wraps a value in a Success.  fail just wraps a message in a Failure.

1 2 3 4 5 let succeed x =     Success (x)   let fail f =     Failure f

You can see that <!> is used only once and to begin with, but only with this CreateNerd function and multiple Apply Functions.  So lets take a look at both of those.

1 2 3 4 5 6 7 8 type Nerd = {id:int; FirstName:string; LastName:string;     Phone:string; EmailAddress:string; StreetAddressId:int; FavoriteGames:string}     let CreateNerd id firstName lastName phone emailAddress streetAddressId favGames =     {id = id; FirstName = firstName; LastName = lastName; Phone = phone;         EmailAddress = emailAddress; StreetAddressId = streetAddressId;         FavoriteGames = favGames}

So, we can see that Create Nerd simply takes in all of the parameters of a nerd and returns a complete nerd ;).  So that means that Apply must be taking all of the functions, aggregating failures and results and either returning a failure list or individual unwrapped values that have been validated!  THATS RIGHT!  It even gives you errors if you line up incorrect values or don’t have enough functions returning values. (I love Visual Studio).

Applying

So lets dig in a little bit more…

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /// given a function wrapped in a result /// and a value wrapped in a result /// apply the function to the value only if both are Success let applyR f result =     match f,result with     | Success f, Success x ->         f x |> Success     | Failure errs, Success (_)     | Success (_), Failure errs ->         errs |> Failure     | Failure errs1, Failure errs2 ->         errs1 @ errs2 |> Failure   /// infix version of apply let (<*>) = applyR

So the comments explain EXACTLY what is going on, but maybe not quite how I think.  In a single parameter instance, makes perfect sense, f is a function, which we wrapped up in a success (see <!>), this is our Success<CreateNerd>.  x is the result of our next line, in this instance

1 2 CreateNerd <!> succeed 1

we are simply wrapping 1 in a Success.  So we apply the function CreateNerd to 1.  Well, this isn’t going to work, CreateNerd takes a ton of parameters, this would however work for a function that takes only a single parameter.

Bringing it All Together

This is where <*> saves the day in conjunction with just functions that return result<vals>

1 2 3 4 5 6 7 8 CreateNerd <!> succeed 1 <*> FormatHelper.CreateFirstName "D" <*> FormatHelper.CreateLastName "A" <*> succeed "9802286278" <*> succeed "david@david.com" <*> succeed 1 <*> succeed "1,2,4"

So it will take both functions, in our example above, lines 3 and 4.  Run both individually and match against a Success.  If both are success, it unwraps both successes and then rewraps them together as a success.  So we would go from Success<D> Success<A> to Success<D; A>. So <*> allows us to chain multiple functions together, unwrapping each one and Applying the next one to it.  We finally lift those into our first function!

Now that was the happy path (I think the more complicated one to think through).

On the unhappy path, it does the same exact thing, but wraps every portion in a Failure, and drops successes and simply returns the Failure List.  So if we had the last 3 lines fail with the message [IncorrectEmail], [IncorrectAddress], [IncorrectGameIds], instead of returning a CompleteNerd, we would return a list of FailureMessages wrapped in a Failure result.

So if we think of this in the context of a Web API controller, GET(id), you can do all of the gets, wrapping everything in success or failure.  You return a Result to the controller.  It matches that against Success or Failure.  In the Success case, just send that json back.  In a Failure, change your return code, do your logging, map those messages to something user friendly, send some html to inject down with those and call it a day :).

Summary

We covered how to use Applicatives and Lifting to deal with validating the construction of a single type.  There are other obvious non validation use cases.  This is directly useful when building WebAPIs and other single execution pieces of code.  The next article will cover dealing with sequences of types.

Again, special shout out to https://fsharpforfunandprofit.com/rop/ and Scott for helping me wrap my brain around this stuff.

Again, you can download the entire script file here: https://onedrive.live.com/redir?resid=478A64BC5718DA47\!299\&authkey=\!ALbqUC1LcqWQevo\&ithint=file%2cfsx