Skip to content

Instantly share code, notes, and snippets.

@afsalthaj
Last active July 29, 2018 07:56
Show Gist options
  • Save afsalthaj/62e1aca4df5bd420b77bbe013d5e2f17 to your computer and use it in GitHub Desktop.
Save afsalthaj/62e1aca4df5bd420b77bbe013d5e2f17 to your computer and use it in GitHub Desktop.

Create a DbOperation algebra

  • A getOperation.. this can return an option of v
  • A writeOperation(key, value, modeOfWriting) … this can return a Result . The Result should encapsulate the actual return value and the mode. The Mode can be Overwrite (which can be Replaced or New or Failed), Insert (which can be New or Failed) or Replace (which can be Wrote or Failed). Feel free to represent this in any way which is type safe.
  • A deleteOperation returning a unit.
  • The Result also has FaiureFallBackOperations or IfSuccess encoded in as well. This may or may not be used by the clients using DbOperation.
  • A readAll returning a Page, representing a list of value and the next key.

Problem Statments

Let us expose the above operation as a Table because User doesn’t need to handle any of the DbOperations directly. Table makes use of the above DbOperation, but with a few extra logic.

  • Get - uses the get operation, however depending on the value returned it should specify how to handle the result. A Handle can be asking the user to PrintLn, Concatenate with a fixed String, WriteResultsToDb (I don't know ..something).
  • The user can then do (in a different space, time) to actually do the handle operation. However Table won't do this, it will tell the user of the table to do what next.
  • WriteOperation. Depending on the Result of operation (which means mode) you need to send a notification to admin which can be sending an email, sending a message or only logging it. The option to choose depends on the few runtime results with in the Put operation. Upto you to define this logic. [Constraint: Table operation should never do any notifications by any chance although, Table knows about Notification]
  • Replace Operation (the mode of writing is always Replace). If Mode == Fail, send a notification. If Mode is Replace success, then create an entry in success control table. Else create an entry in failure control table. [Constraint: Table operation should never do these inserts. Please defere these callback/feedback operations as much as possible.]
  • Delete operation (simply makes use of the above opertaion).
  • readAll returns page and an AnotherHandler. Example, based on the contents of the page, or size of page, or based on some internal db operation, return the callback operation Write to a File or Write to InMemory mutable map. Feel free to think we have dozens of different Handlers or CallBacks, which is returned by different functions.

Table can do the following processes as well

  • updateAndDelete
  • updateAndThenRead
  • deleteAndThenPut

.

Feel free to add a variety of operations like this.

Please do remember that, in test cases we need to do all combinations. Example: We need to test what happens if we update after a delete.

Add one test case val r = table.readAll if (r.page.size > 100) else . [constraint: You shouldn't need to match output of a mock file-write or inmemory-write in this process]


An Inmemory which doesn't need to be in ZIO/IO.

Feel free to add InMemory which doesn’t have anything to do with IO, and everything is without effects. Basically the effect that it will handle is Invalid \/ ?. (Gels well with Finally tagless and Free, but worth trying)

An Interpreter helper

Be it finally tagless or Free Monad, we can easily anticipate an object that consist of actual Azure related operations which can be under the effect EitherT[Kleisli[F, AzureClient, ?], Invalid, ?]. Feel free to represent Invalid in any way. Invalid can be a universal error of the app. There can be an engineering done in this space, but leaving it at this stage.


Object ActualAzureOperation {
    type AzureAction = EitherT[Kleisli[F, AzureClient, ?], Invalid, ?]
    def createTable = AzureAction[A]
    def write: AzureAction[Result[V, Mode]]
   
    def delete:  AzureAction[Result[V, Mode]
    ... and so on!
}


Hints and notes

  • All Handlers mentioned in the problem can be a Free algebra? May be yes, may be no. Let's see.

  • The DbOperation is another Free algebra, and Table functions hence return a Free Monad.

  • A natural transformation from f1: DbOperation ~> AzureAction will exist for sure.

  • A transformation which converts f2: DbOperation ~> C to f3: DbAction ~> C will exist for sure, where type DbAction = Free[DbOperation, A]. Please note DbAction becomes secondary here. We cared only DbOperation and we get DbAction ~> C for free.

  • table.put(data) seem to return DbAction. So how do we test out the actual interpreter which is some AzureAction? .Well, use f1 and compose it with a function that goes from AzureAction ~> Invalid \/ A resulting in DbOperation ~> Invalid \/ A. Again, it makes intuitive sense that we are going from just DbOperation ~> Invalid \/ A. We don’t care other effects in test cases.

  • For in memory all that I need to think of is not F, but a DbOperation ~> Invalid \/ A. As mentioned before, given a DbOperation ~> Invalid \/ A, we can inductively get f: DbAction ~> Invalid \/ A. Hence f(table.delete(data)) returns Invalid \/ A.

  • Also think when the return type of an operation is another operation. Example: An operations returns a dependent callback handler. In Free thinking, this operation is another simple algebra that are devoid of effects. In Free thinking, this means DbAction will contain, for example, Notification[Unit]. We can use DbAction ~> Invalid \/ A and NotificationAction ~> Invalid \/ B (obtained from NotificationOp ~> Invalid \/ A) and then merge to get Invalid \/ (A, B). Oh well! We actually don't need to do Notification yet. Defer it further. Example: DbAction ~> Invalid \/ Notification[Unit] first and then and pass the result to next operation. NotificationAction ~> Invalid \/ Unit. At this point, I see an advantage in my test cases. (Again, I haven't thought how would it look like in finally tagless. Free algebrae allowed me to reach the solution more intuitively. I need to "think" to solve this in inheritence mode.).

  • Write test cases interpretrer that goes from DbAction ~> Invalid \/ A given a DbOperation ~> AwsAction.

Natural transformation are super flexible + you can do pattern matching on operations directly , and of course being able to pass the individual components of the algebra (program) with least number of type parameters.

Actual code answer is here


A simpler problem if the above one is time consuming.

Here goes another question and answer that proves Free is better. The problem is inspired from a blog, however it took some time to make it compile and that's mostly because of my ignorance.

The (question and) solution below actually inspects a program, test if it is doing the right thing, including the order of executions.

https://gist.github.com/afsalthaj/110e01322cc05ef6c7a1e6fa45e95dea

I have also tried to do the same thing with Finally Tagless and I lost the intuitive thinking of the problem - the problem of inspecting a program.

https://gist.github.com/afsalthaj/9a59ed8ae4b7b1c238759a85af5ccf45

I have a general feeling now that a program can have both patterns and is completely justified. Free can sadly come up with a big bunch of boilerplates due to the entry of Coproducts concept, but it isn't a good justification to remove it in places in the program where it can nicely work. In summary, both patterns can solve your problem on a high level, however, both patterns are not completely isomorphic to each other, especially when considering the power of testability/correctness through inspection of a program and the actual deferring of execution (not just "delaying") that Free brings in (intuitively).

@afsalthaj
Copy link
Author

I am calling Free better for this program, because a program is returning lot many programs here, essentially giving you control over when to execute the deffered execution. A slight change in the usecase will make finally tagless win the game if you think about it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment