layout: post
title: “Reinventing the Reader monad”
description: “Or, designing your own elevated world”
categories: [“Patterns”]
seriesId: “Map and Bind and Apply, Oh my!”

seriesOrder: 6

This post is the sixth in a series.
In the first two posts, I described some of the core functions for dealing with generic data types: map, bind, and so on.
In the third post, I discussed “applicative” vs “monadic” style, and how to lift values and functions to be consistent with each other.
In the fourth and previous posts, I introduced traverse and sequence
as a way of working with lists of elevated values, and we saw this used in a practical example: downloading some URLs.

In this post, we’ll finish up by working through another practical example, but this time we’ll create our own “elevated world” as a way to deal with awkward code.
We’ll see that this approach is so common that it has a name — the “Reader monad”.

Series contents

Here’s a list of shortcuts to the various functions mentioned in this series:


Part 6: Designing your own elevated world

The scenario we’ll be working with in this post is just this:

A customer comes to your site and wants to view information about the products they have purchased.

In this example, we’ll assume that you have a API for a key/value store (such as Redis or a NoSql database), and all the information
you need is stored there.

So the code we need will look something like this:

  1. Open API connection
  2. Get product ids purchased by customer id using the API
  3. For each product id:
  4. get the product info for that id using the API
  5. Close API connection
  6. Return the list of product infos

How hard can that be?

Well, it turns out to be surprisingly tricky! Luckily, we can find a way to make it easier using the concepts in this series.


Defining the domain and a dummy ApiClient

First let’s define the domain types:

  • There will be a CustomerId and ProductId of course.
  • For the product information, we’ll just define a simple ProductInfo with a ProductName field.

Here are the types:

  1. type CustId = CustId of string
  2. type ProductId = ProductId of string
  3. type ProductInfo = {ProductName: string; }

For testing our api, let’s create an ApiClient class with some Get and Set methods, backed by a static mutable dictionary.
This is based on similar APIs such as the Redis client.

Notes:

  • The Get and Set both work with objects, so I’ve added a casting mechanism.
  • In case of errors such as a failed cast, or a missing key, I’m using the Result type that we’ve been using throughout this series.
    Therefore, both Get and Set return Results rather than plain objects.
  • To make it more realistic, I’ve also added dummy methods for Open, Close and Dispose.
  • All methods trace a log to the console.
  1. type ApiClient() =
  2. // static storage
  3. static let mutable data = Map.empty<string,obj>
  4. /// Try casting a value
  5. /// Return Success of the value or Failure on failure
  6. member private this.TryCast<'a> key (value:obj) =
  7. match value with
  8. | :? 'a as a ->
  9. Result.Success a
  10. | _ ->
  11. let typeName = typeof<'a>.Name
  12. Result.Failure [sprintf "Can't cast value at %s to %s" key typeName]
  13. /// Get a value
  14. member this.Get<'a> (id:obj) =
  15. let key = sprintf "%A" id
  16. printfn "[API] Get %s" key
  17. match Map.tryFind key data with
  18. | Some o ->
  19. this.TryCast<'a> key o
  20. | None ->
  21. Result.Failure [sprintf "Key %s not found" key]
  22. /// Set a value
  23. member this.Set (id:obj) (value:obj) =
  24. let key = sprintf "%A" id
  25. printfn "[API] Set %s" key
  26. if key = "bad" then // for testing failure paths
  27. Result.Failure [sprintf "Bad Key %s " key]
  28. else
  29. data <- Map.add key value data
  30. Result.Success ()
  31. member this.Open() =
  32. printfn "[API] Opening"
  33. member this.Close() =
  34. printfn "[API] Closing"
  35. interface System.IDisposable with
  36. member this.Dispose() =
  37. printfn "[API] Disposing"

Let’s do some tests:

  1. do
  2. use api = new ApiClient()
  3. api.Get "K1" |> printfn "[K1] %A"
  4. api.Set "K2" "hello" |> ignore
  5. api.Get<string> "K2" |> printfn "[K2] %A"
  6. api.Set "K3" "hello" |> ignore
  7. api.Get<int> "K3" |> printfn "[K3] %A"

And the results are:

  1. [API] Get "K1"
  2. [K1] Failure ["Key "K1" not found"]
  3. [API] Set "K2"
  4. [API] Get "K2"
  5. [K2] Success "hello"
  6. [API] Set "K3"
  7. [API] Get "K3"
  8. [K3] Failure ["Can't cast value at "K3" to Int32"]
  9. [API] Disposing


A first implementation attempt

For our first attempt at implementing the scenario, let’s start with the pseudo-code from above:

  1. let getPurchaseInfo (custId:CustId) : Result<ProductInfo list> =
  2. // Open api connection
  3. use api = new ApiClient()
  4. api.Open()
  5. // Get product ids purchased by customer id
  6. let productIdsResult = api.Get<ProductId list> custId
  7. let productInfosResult = ??
  8. // Close api connection
  9. api.Close()
  10. // Return the list of product infos
  11. productInfosResult

So far so good, but there is a bit of a problem already.

The getPurchaseInfo function takes a CustId as input, but it can’t just output a list of ProductInfos, because there might be a failure.
That means that the return type needs to be Result<ProductInfo list>.

Ok, how do we create our productInfosResult?

Well that should be easy. If the productIdsResult is Success, then loop through each id and get the info for each id.
If the productIdsResult is Failure, then just return that failure.

  1. let getPurchaseInfo (custId:CustId) : Result<ProductInfo list> =
  2. // Open api connection
  3. use api = new ApiClient()
  4. api.Open()
  5. // Get product ids purchased by customer id
  6. let productIdsResult = api.Get<ProductId list> custId
  7. let productInfosResult =
  8. match productIdsResult with
  9. | Success productIds ->
  10. let productInfos = ResizeArray() // Same as .NET List<T>
  11. for productId in productIds do
  12. let productInfo = api.Get<ProductInfo> productId
  13. productInfos.Add productInfo // mutation!
  14. Success productInfos
  15. | Failure err ->
  16. Failure err
  17. // Close api connection
  18. api.Close()
  19. // Return the list of product infos
  20. productInfosResult

Hmmm. It’s looking a bit ugly. And I’m having to use a mutable data structure (productInfos) to accumulate each product info and then wrap it in Success.

And there’s a worse problem The productInfo that I’m getting from api.Get<ProductInfo> is not a ProductInfo at all, but a Result<ProductInfo>,
so productInfos is not the right type at all!

Let’s add code to test each ProductInfo result. If it’s a success, then add it to the list of product infos, and if it’s a failure, then return the failure.

  1. let getPurchaseInfo (custId:CustId) : Result<ProductInfo list> =
  2. // Open api connection
  3. use api = new ApiClient()
  4. api.Open()
  5. // Get product ids purchased by customer id
  6. let productIdsResult = api.Get<ProductId list> custId
  7. let productInfosResult =
  8. match productIdsResult with
  9. | Success productIds ->
  10. let productInfos = ResizeArray() // Same as .NET List<T>
  11. let mutable anyFailures = false
  12. for productId in productIds do
  13. let productInfoResult = api.Get<ProductInfo> productId
  14. match productInfoResult with
  15. | Success productInfo ->
  16. productInfos.Add productInfo
  17. | Failure err ->
  18. Failure err
  19. Success productInfos
  20. | Failure err ->
  21. Failure err
  22. // Close api connection
  23. api.Close()
  24. // Return the list of product infos
  25. productInfosResult

Um, no. That won’t work at all. The code above will not compile. We can’t do an “early return” in the loop when a failure happens.

So what do we have so far? Some really ugly code that won’t even compile.

There has to be a better way.


A second implementation attempt

It would be great if we could hide all this unwrapping and testing of Results. And there is — computation expressions to the rescue.

If we create a computation expression for Result we can write the code like this:

  1. /// CustId -> Result<ProductInfo list>
  2. let getPurchaseInfo (custId:CustId) : Result<ProductInfo list> =
  3. // Open api connection
  4. use api = new ApiClient()
  5. api.Open()
  6. let productInfosResult = Result.result {
  7. // Get product ids purchased by customer id
  8. let! productIds = api.Get<ProductId list> custId
  9. let productInfos = ResizeArray() // Same as .NET List<T>
  10. for productId in productIds do
  11. let! productInfo = api.Get<ProductInfo> productId
  12. productInfos.Add productInfo
  13. return productInfos |> List.ofSeq
  14. }
  15. // Close api connection
  16. api.Close()
  17. // Return the list of product infos
  18. productInfosResult

In let productInfosResult = Result.result { .. } code we create a result computation expression that simplifies all the unwrapping (with let!) and wrapping (with return).

And so this implementation has no explicit xxxResult values anywhere. However, it still has to use a mutable collection class to do the accumulation,
because the for productId in productIds do is not actually a real for loop, and we can’t replace it with List.map, say.

The result computation expression.

Which brings us onto the implementation of the result computation expression. In the previous posts, ResultBuilder only had two methods, Return and Bind,
but in order to get the for..in..do functionality, we have to implement a lot of other methods too, and it ends up being a bit more complicated.

  1. module Result =
  2. let bind f xResult = ...
  3. type ResultBuilder() =
  4. member this.Return x = retn x
  5. member this.ReturnFrom(m: Result<'T>) = m
  6. member this.Bind(x,f) = bind f x
  7. member this.Zero() = Failure []
  8. member this.Combine (x,f) = bind f x
  9. member this.Delay(f: unit -> _) = f
  10. member this.Run(f) = f()
  11. member this.TryFinally(m, compensation) =
  12. try this.ReturnFrom(m)
  13. finally compensation()
  14. member this.Using(res:#System.IDisposable, body) =
  15. this.TryFinally(body res, fun () ->
  16. match res with
  17. | null -> ()
  18. | disp -> disp.Dispose())
  19. member this.While(guard, f) =
  20. if not (guard()) then
  21. this.Zero()
  22. else
  23. this.Bind(f(), fun _ -> this.While(guard, f))
  24. member this.For(sequence:seq<_>, body) =
  25. this.Using(sequence.GetEnumerator(), fun enum ->
  26. this.While(enum.MoveNext, this.Delay(fun () ->
  27. body enum.Current)))
  28. let result = new ResultBuilder()

I have a series about the internals of computation expressions,
so I don’t want to explain all that code here. Instead, for the rest of the post
we’ll work on refactoring getPurchaseInfo, and by the end of it we’ll see that we don’t need the result computation expression at all.


Refactoring the function

The problem with the getPurchaseInfo function as it stands is that it mixes concerns: it both creates the ApiClient and does some work with it.

There a number of problems with this approach:

  • If we want to do different work with the API, we have to repeat the open/close part of this code.
    And it’s possible that one of the implementations might open the API but forget to close it.
  • It’s not testable with a mock API client.

We can solve both of these problems by separating the creation of an ApiClient from its use by parameterizing the action, like this.

  1. let executeApiAction apiAction =
  2. // Open api connection
  3. use api = new ApiClient()
  4. api.Open()
  5. // do something with it
  6. let result = apiAction api
  7. // Close api connection
  8. api.Close()
  9. // return result
  10. result

The action function that is passed in would look like this, with a parameter for the ApiClient as well as for the CustId:

  1. /// CustId -> ApiClient -> Result<ProductInfo list>
  2. let getPurchaseInfo (custId:CustId) (api:ApiClient) =
  3. let productInfosResult = Result.result {
  4. let! productIds = api.Get<ProductId list> custId
  5. let productInfos = ResizeArray() // Same as .NET List<T>
  6. for productId in productIds do
  7. let! productInfo = api.Get<ProductInfo> productId
  8. productInfos.Add productInfo
  9. return productInfos |> List.ofSeq
  10. }
  11. // return result
  12. productInfosResult

Note that getPurchaseInfo has two parameters, but executeApiAction expects a function with only one.

No problem! Just use partial application to bake in the first parameter:

  1. let action = getPurchaseInfo (CustId "C1") // partially apply
  2. executeApiAction action

That’s why the ApiClient is the second parameter in the parameter list — so that we can do partial application.

More refactoring

We might need to get the product ids for some other purpose, and also the productInfo, so let’s refactor those out into separate functions too:

  1. /// CustId -> ApiClient -> Result<ProductId list>
  2. let getPurchaseIds (custId:CustId) (api:ApiClient) =
  3. api.Get<ProductId list> custId
  4. /// ProductId -> ApiClient -> Result<ProductInfo>
  5. let getProductInfo (productId:ProductId) (api:ApiClient) =
  6. api.Get<ProductInfo> productId
  7. /// CustId -> ApiClient -> Result<ProductInfo list>
  8. let getPurchaseInfo (custId:CustId) (api:ApiClient) =
  9. let result = Result.result {
  10. let! productIds = getPurchaseIds custId api
  11. let productInfos = ResizeArray()
  12. for productId in productIds do
  13. let! productInfo = getProductInfo productId api
  14. productInfos.Add productInfo
  15. return productInfos |> List.ofSeq
  16. }
  17. // return result
  18. result

Now, we have these nice core functions getPurchaseIds and getProductInfo, but I’m annoyed that I have to write messy code to glue them together in getPurchaseInfo.

Ideally, what I’d like to do is pipe the output of getPurchaseIds into getProductInfo like this:

  1. let getPurchaseInfo (custId:CustId) =
  2. custId
  3. |> getPurchaseIds
  4. |> List.map getProductInfo

Or as a diagram:

Reinventing the Reader monad - 图1

But I can’t, and there are two reasons why:

  • First, getProductInfo has two parameters. Not just a ProductId but also the ApiClient.
  • Second, even if ApiClient wasn’t there, the input of getProductInfo is a simple ProductId but the output of getPurchaseIds is a Result.

Wouldn’t it be great if we could solve both of these problems!


Introducing our own elevated world

Let’s address the first problem. How can we compose functions when the extra ApiClient parameter keeps getting in the way?

This is what a typical API calling function looks like:

Reinventing the Reader monad - 图2

If we look at the type signature we see this, a function with two parameters:

Reinventing the Reader monad - 图3

But another way to interpret this function is as a function with one parameter that returns another function. The returned function has an ApiClient parameter
and returns the final ouput.

Reinventing the Reader monad - 图4

You might think of it like this: I have an input right now, but I won’t have an actual ApiClient until later,
so let me use the input to create a api-consuming function that can I glue together in various ways right now, without needing a ApiClient at all.

Let’s give this api-consuming function a name. Let’s call it ApiAction.

Reinventing the Reader monad - 图5

In fact, let’s do more than that — let’s make it a type!

  1. type ApiAction<'a> = (ApiClient -> 'a)

Unfortunately, as it stands, this is just a type alias for a function, not a separate type.
We need to wrap it in a single case union to make it a distinct type.

  1. type ApiAction<'a> = ApiAction of (ApiClient -> 'a)

Rewriting to use ApiAction

Now that we have a real type to use, we can rewrite our core domain functions to use it.

First getPurchaseIds:

  1. // CustId -> ApiAction<Result<ProductId list>>
  2. let getPurchaseIds (custId:CustId) =
  3. // create the api-consuming function
  4. let action (api:ApiClient) =
  5. api.Get<ProductId list> custId
  6. // wrap it in the single case
  7. ApiAction action

The signature is now CustId -> ApiAction<Result<ProductId list>>, which you can interpret as meaning: “give me a CustId and I will give a you a ApiAction that, when
given an api, will make a list of ProductIds”.

Similarly, getProductInfo can be rewritten to return an ApiAction:

  1. // ProductId -> ApiAction<Result<ProductInfo>>
  2. let getProductInfo (productId:ProductId) =
  3. // create the api-consuming function
  4. let action (api:ApiClient) =
  5. api.Get<ProductInfo> productId
  6. // wrap it in the single case
  7. ApiAction action

Notice those signatures:

  • CustId -> ApiAction<Result<ProductId list>>
  • ProductId -> ApiAction<Result<ProductInfo>>

This is starting to look awfully familiar. Didn’t we see something just like this in the previous post, with Async<Result<_>>?

ApiAction as an elevated world

If we draw diagrams of the various types involved in these two functions, we can clearly see that ApiAction is an elevated world, just like List and Result.
And that means that we should be able to use the same techniques as we have used before: map, bind, traverse, etc.

Here’s getPurchaseIds as a stack diagram. The input is a CustId and the output is an ApiAction<Result<List<ProductId>>>:

Reinventing the Reader monad - 图6

and with getProductInfo the input is a ProductId and the output is an ApiAction<Result<ProductInfo>>:

Reinventing the Reader monad - 图7

The combined function that we want, getPurchaseInfo, should look like this:

Reinventing the Reader monad - 图8

And now the problem in composing the two functions is very clear: the output of getPurchaseIds can not be used as the input for getProductInfo:

Reinventing the Reader monad - 图9

But I think that you can see that we have some hope! There should be some way of manipulating these layers so that they do match up, and then we can compose them easily.

So that’s what we will work on next.

Introducting ApiActionResult

In the last post we merged Async and Result into the compound type AsyncResult. We can do the same here, and create the type ApiActionResult.

When we make this change, our two functions become slightly simpler:

Reinventing the Reader monad - 图10

Enough diagrams — let’s write some code now.

First, we need to define map, apply, return and bind for ApiAction:

  1. module ApiAction =
  2. /// Evaluate the action with a given api
  3. /// ApiClient -> ApiAction<'a> -> 'a
  4. let run api (ApiAction action) =
  5. let resultOfAction = action api
  6. resultOfAction
  7. /// ('a -> 'b) -> ApiAction<'a> -> ApiAction<'b>
  8. let map f action =
  9. let newAction api =
  10. let x = run api action
  11. f x
  12. ApiAction newAction
  13. /// 'a -> ApiAction<'a>
  14. let retn x =
  15. let newAction api =
  16. x
  17. ApiAction newAction
  18. /// ApiAction<('a -> 'b)> -> ApiAction<'a> -> ApiAction<'b>
  19. let apply fAction xAction =
  20. let newAction api =
  21. let f = run api fAction
  22. let x = run api xAction
  23. f x
  24. ApiAction newAction
  25. /// ('a -> ApiAction<'b>) -> ApiAction<'a> -> ApiAction<'b>
  26. let bind f xAction =
  27. let newAction api =
  28. let x = run api xAction
  29. run api (f x)
  30. ApiAction newAction
  31. /// Create an ApiClient and run the action on it
  32. /// ApiAction<'a> -> 'a
  33. let execute action =
  34. use api = new ApiClient()
  35. api.Open()
  36. let result = run api action
  37. api.Close()
  38. result

Note that all the functions use a helper function called run which unwraps an ApiAction to get the function inside,
and applies this to the api that is also passed in. The result is the value wrapped in the ApiAction.

For example, if we had an ApiAction<int> then run api myAction would result in an int.

And at the bottom, there is a execute function that creates an ApiClient, opens the connection, runs the action, and then closes the connection.

And with the core functions for ApiAction defined, we can go ahead and define the functions for the compound type ApiActionResult,
just as we did for AsyncResult in the previous post:

  1. module ApiActionResult =
  2. let map f =
  3. ApiAction.map (Result.map f)
  4. let retn x =
  5. ApiAction.retn (Result.retn x)
  6. let apply fActionResult xActionResult =
  7. let newAction api =
  8. let fResult = ApiAction.run api fActionResult
  9. let xResult = ApiAction.run api xActionResult
  10. Result.apply fResult xResult
  11. ApiAction newAction
  12. let bind f xActionResult =
  13. let newAction api =
  14. let xResult = ApiAction.run api xActionResult
  15. // create a new action based on what xResult is
  16. let yAction =
  17. match xResult with
  18. | Success x ->
  19. // Success? Run the function
  20. f x
  21. | Failure err ->
  22. // Failure? wrap the error in an ApiAction
  23. (Failure err) |> ApiAction.retn
  24. ApiAction.run api yAction
  25. ApiAction newAction

Working out the transforms

Now that we have all the tools in place, we must decide on what transforms to use to change the shape of getProductInfo so that the input matches up.

Should we choose map, or bind, or traverse?

Let’s play around with the stacks visually and see what happens for each kind of transform.

Before we get started, let’s be explicit about what we are trying to achieve:

  • We have two functions getPurchaseIds and getProductInfo that we want to combine into a single function getPurchaseInfo.
  • We have to manipulate the left side (the input) of getProductInfo so that it matches the output of getPurchaseIds.
  • We have to manipulate the right side (the output) of getProductInfo so that it matches the output of our ideal getPurchaseInfo.

Reinventing the Reader monad - 图11

Map

As a reminder, map adds a new stack on both sides. So if we start with a generic world-crossing function like this:

Reinventing the Reader monad - 图12

Then, after List.map say, we will have a new List stack on each site.

Reinventing the Reader monad - 图13

Here’s our getProductInfo before transformation:

Reinventing the Reader monad - 图14

And here is what it would look like after using List.map

Reinventing the Reader monad - 图15

This might seem promising — we have a List of ProductId as input now, and if we can stack a ApiActionResult on top we would match the output of getPurchaseId.

But the output is all wrong. We want the ApiActionResult to stay on the top. That is, we don’t want a List of ApiActionResult but a ApiActionResult of List.

Bind

Ok, what about bind?

If you recall, bind turns a “diagonal” function into a horizontal function by adding a new stack on the left sides. So for example,
whatever the top elevated world is on the right, that will be added to the left.

Reinventing the Reader monad - 图16

Reinventing the Reader monad - 图17

And here is what our getProductInfo would look like after using ApiActionResult.bind

Reinventing the Reader monad - 图18

This is no good to us. We need to have a List of ProductId as input.

Traverse

Finally, let’s try traverse.

traverse turns a diagonal function of values into diagonal function with lists wrapping the values. That is, List is added as the top stack
on the left hand side, and the second-from-top stack on the right hand side.

Reinventing the Reader monad - 图19

Reinventing the Reader monad - 图20

if we try that out on getProductInfo we get something very promising.

Reinventing the Reader monad - 图21

The input is a list as needed. And the output is perfect. We wanted a ApiAction<Result<List<ProductInfo>>> and we now have it.

So all we need to do now is add an ApiActionResult to the left side.

Well, we just saw this! It’s bind. So if we do that as well, we are finished.

Reinventing the Reader monad - 图22

And here it is expressed as code:

  1. let getPurchaseInfo =
  2. let getProductInfo1 = traverse getProductInfo
  3. let getProductInfo2 = ApiActionResult.bind getProductInfo1
  4. getPurchaseIds >> getProductInfo2

Or to make it a bit less ugly:

  1. let getPurchaseInfo =
  2. let getProductInfoLifted =
  3. getProductInfo
  4. |> traverse
  5. |> ApiActionResult.bind
  6. getPurchaseIds >> getProductInfoLifted

Let’s compare that with the earlier version of getPurchaseInfo:

  1. let getPurchaseInfo (custId:CustId) (api:ApiClient) =
  2. let result = Result.result {
  3. let! productIds = getPurchaseIds custId api
  4. let productInfos = ResizeArray()
  5. for productId in productIds do
  6. let! productInfo = getProductInfo productId api
  7. productInfos.Add productInfo
  8. return productInfos |> List.ofSeq
  9. }
  10. // return result
  11. result

Let’s compare the two versions in a table:






















Earlier versonLatest function
Composite function is non-trivial and needs special code to glue the two smaller functions togetherComposite function is just piping and composition
Uses the “result” computation expression No special syntax needed
Has special code to loop through the results Uses “traverse”
Uses a intermediate (and mutable) List object to accumulate the list of product infos No intermediate values needed. Just a data pipeline.

Implementing traverse

The code above uses traverse, but we haven’t implemented it yet.
As I noted earlier, it can be implemented mechanically, following a template.

Here it is:

  1. let traverse f list =
  2. // define the applicative functions
  3. let (<*>) = ApiActionResult.apply
  4. let retn = ApiActionResult.retn
  5. // define a "cons" function
  6. let cons head tail = head :: tail
  7. // right fold over the list
  8. let initState = retn []
  9. let folder head tail =
  10. retn cons <*> f head <*> tail
  11. List.foldBack folder list initState

Testing the implementation

Let’s test it!

First we need a helper function to show results:

  1. let showResult result =
  2. match result with
  3. | Success (productInfoList) ->
  4. printfn "SUCCESS: %A" productInfoList
  5. | Failure errs ->
  6. printfn "FAILURE: %A" errs

Next, we need to load the API with some test data:

  1. let setupTestData (api:ApiClient) =
  2. //setup purchases
  3. api.Set (CustId "C1") [ProductId "P1"; ProductId "P2"] |> ignore
  4. api.Set (CustId "C2") [ProductId "PX"; ProductId "P2"] |> ignore
  5. //setup product info
  6. api.Set (ProductId "P1") {ProductName="P1-Name"} |> ignore
  7. api.Set (ProductId "P2") {ProductName="P2-Name"} |> ignore
  8. // P3 missing
  9. // setupTestData is an api-consuming function
  10. // so it can be put in an ApiAction
  11. // and then that apiAction can be executed
  12. let setupAction = ApiAction setupTestData
  13. ApiAction.execute setupAction
  • Customer C1 has purchased two products: P1 and P2.
  • Customer C2 has purchased two products: PX and P2.
  • Products P1 and P2 have some info.
  • Product PX does not have any info.

Let’s see how this works out for different customer ids.

We’ll start with Customer C1. For this customer we expect both product infos to be returned:

  1. CustId "C1"
  2. |> getPurchaseInfo
  3. |> ApiAction.execute
  4. |> showResult

And here are the results:

  1. [API] Opening
  2. [API] Get CustId "C1"
  3. [API] Get ProductId "P1"
  4. [API] Get ProductId "P2"
  5. [API] Closing
  6. [API] Disposing
  7. SUCCESS: [{ProductName = "P1-Name";}; {ProductName = "P2-Name";}]

What happens if we use a missing customer, such as CX?

  1. CustId "CX"
  2. |> getPurchaseInfo
  3. |> ApiAction.execute
  4. |> showResult

As expected, we get a nice “key not found” failure, and the rest of the operations are skipped as soon as the key is not found.

  1. [API] Opening
  2. [API] Get CustId "CX"
  3. [API] Closing
  4. [API] Disposing
  5. FAILURE: ["Key CustId "CX" not found"]

What about if one of the purchased products has no info? For example, customer C2 purchased PX and P2, but there is no info for PX.

  1. CustId "C2"
  2. |> getPurchaseInfo
  3. |> ApiAction.execute
  4. |> showResult

The overall result is a failure. Any bad product causes the whole operation to fail.

  1. [API] Opening
  2. [API] Get CustId "C2"
  3. [API] Get ProductId "PX"
  4. [API] Get ProductId "P2"
  5. [API] Closing
  6. [API] Disposing
  7. FAILURE: ["Key ProductId "PX" not found"]

But note that the data for product P2 is fetched even though product PX failed. Why? Because we are using the applicative version of traverse,
so every element of the list is fetched “in parallel”.

If we wanted to only fetch P2 once we knew that PX existed, then we should be using monadic style instead. We already seen how to write a monadic version of traverse,
so I leave that as an exercise for you!


Filtering out failures

In the implementation above, the getPurchaseInfo function failed if any product failed to be found. Harsh!

A real application would probably be more forgiving. Probably what should happen is that the failed products are logged, but all the successes are accumulated
and returned.

How could we do this?

The answer is simple — we just need to modify the traverse function to skip failures.

First, we need to create a new helper function for ApiActionResult. It will allow us to pass in two functions, one for the success case
and one for the error case:

  1. module ApiActionResult =
  2. let map = ...
  3. let retn = ...
  4. let apply = ...
  5. let bind = ...
  6. let either onSuccess onFailure xActionResult =
  7. let newAction api =
  8. let xResult = ApiAction.run api xActionResult
  9. let yAction =
  10. match xResult with
  11. | Result.Success x -> onSuccess x
  12. | Result.Failure err -> onFailure err
  13. ApiAction.run api yAction
  14. ApiAction newAction

This helper function helps us match both cases inside a ApiAction without doing complicated unwrapping. We will need this for our traverse that skips failures.

By the way, note that ApiActionResult.bind can be defined in terms of either:

  1. let bind f =
  2. either
  3. // Success? Run the function
  4. (fun x -> f x)
  5. // Failure? wrap the error in an ApiAction
  6. (fun err -> (Failure err) |> ApiAction.retn)

Now we can define our “traverse with logging of failures” function:

  1. let traverseWithLog log f list =
  2. // define the applicative functions
  3. let (<*>) = ApiActionResult.apply
  4. let retn = ApiActionResult.retn
  5. // define a "cons" function
  6. let cons head tail = head :: tail
  7. // right fold over the list
  8. let initState = retn []
  9. let folder head tail =
  10. (f head)
  11. |> ApiActionResult.either
  12. (fun h -> retn cons <*> retn h <*> tail)
  13. (fun errs -> log errs; tail)
  14. List.foldBack folder list initState

The only difference between this and the previous implementation is this bit:

  1. let folder head tail =
  2. (f head)
  3. |> ApiActionResult.either
  4. (fun h -> retn cons <*> retn h <*> tail)
  5. (fun errs -> log errs; tail)

This says that:

  • If the new first element (f head) is a success, lift the inner value (retn h) and cons it with the tail to build a new list.
  • But if the new first element is a failure, then log the inner errors (errs) with the passed in logging function (log)
    and just reuse the current tail.
    In this way, failed elements are not added to the list, but neither do they cause the whole function to fail.

Let’s create a new function getPurchasesInfoWithLog and try it with customer C2 and the missing product PX:

  1. let getPurchasesInfoWithLog =
  2. let log errs = printfn "SKIPPED %A" errs
  3. let getProductInfoLifted =
  4. getProductInfo
  5. |> traverseWithLog log
  6. |> ApiActionResult.bind
  7. getPurchaseIds >> getProductInfoLifted
  8. CustId "C2"
  9. |> getPurchasesInfoWithLog
  10. |> ApiAction.execute
  11. |> showResult

The result is a Success now, but only one ProductInfo, for P2, is returned. The log shows that PX was skipped.

  1. [API] Opening
  2. [API] Get CustId "C2"
  3. [API] Get ProductId "PX"
  4. SKIPPED ["Key ProductId "PX" not found"]
  5. [API] Get ProductId "P2"
  6. [API] Closing
  7. [API] Disposing
  8. SUCCESS: [{ProductName = "P2-Name";}]


The Reader monad

If you look closely at the ApiResult module, you will see that map, bind, and all the other functions do not use any information about the api
that is passed around. We could have made it any type and those functions would still have worked.

So in the spirit of “parameterize all the things”, why not make it a parameter?

That means that we could have defined ApiAction as follows:

  1. type ApiAction<'anything,'a> = ApiAction of ('anything -> 'a)

But if it can be anything, why call it ApiAction any more? It could represent any set of things that depend on an object
(such as an api) being passed in to them.

We are not the first people to discover this! This type is commonly called the Reader type and is defined like this:

  1. type Reader<'environment,'a> = Reader of ('environment -> 'a)

The extra type 'environment plays the same role that ApiClient did in our definition of ApiAction. There is some environment
that is passed around as an extra parameter to all your functions, just as a api instance was.

In fact, we can actually define ApiAction in terms of Reader very easily:

  1. type ApiAction<'a> = Reader<ApiClient,'a>

The set of functions for Reader are exactly the same as for ApiAction. I have just taken the code and replaced ApiAction with Reader and
api with environment!

  1. module Reader =
  2. /// Evaluate the action with a given environment
  3. /// 'env -> Reader<'env,'a> -> 'a
  4. let run environment (Reader action) =
  5. let resultOfAction = action environment
  6. resultOfAction
  7. /// ('a -> 'b) -> Reader<'env,'a> -> Reader<'env,'b>
  8. let map f action =
  9. let newAction environment =
  10. let x = run environment action
  11. f x
  12. Reader newAction
  13. /// 'a -> Reader<'env,'a>
  14. let retn x =
  15. let newAction environment =
  16. x
  17. Reader newAction
  18. /// Reader<'env,('a -> 'b)> -> Reader<'env,'a> -> Reader<'env,'b>
  19. let apply fAction xAction =
  20. let newAction environment =
  21. let f = run environment fAction
  22. let x = run environment xAction
  23. f x
  24. Reader newAction
  25. /// ('a -> Reader<'env,'b>) -> Reader<'env,'a> -> Reader<'env,'b>
  26. let bind f xAction =
  27. let newAction environment =
  28. let x = run environment xAction
  29. run environment (f x)
  30. Reader newAction

The type signatures are a bit harder to read now though!

The Reader type, plus bind and return, plus the fact that bind and return implement the monad laws, means that Reader is typically called “the Reader monad” .

I’m not going to delve into the Reader monad here, but I hope that you can see how it is actually a useful thing and not some bizarre ivory tower concept.

The Reader monad vs. an explicit type

Now if you like, you could replace all the ApiAction code above with Reader code, and it would work just the same. But should you?

Personally, I think that while understanding the concept behind the Reader monad is important and useful, I prefer the actual implementation of ApiAction
as I defined it originally, an explicit type rather than an alias for Reader<ApiClient,'a>.

Why? Well, F# doesn’t have typeclasses, F# doesn’t have partial application of type constructors, F# doesn’t have “newtype”.
Basically, F# isn’t Haskell! I don’t think that idioms that work well in Haskell should be carried over to F# directly when the language does not offer support for it.

If you understand the concepts, you can implement all the necessary transformations in a few lines of code. Yes, it’s a little extra work, but the
upside is less abstraction and fewer dependencies.

I would make an exception, perhaps, if your team were all Haskell experts, and the Reader monad was familiar to everyone. But for teams of different abilities, I would err on being too concrete
rather than too abstract.

Summary

In this post, we worked through another practical example, created our own elevated world which made things much easier, and in
the process, accidentally re-invented the reader monad.

If you liked this, you can see a similar practical example, this time for the State monad, in my series on “Dr Frankenfunctor and the Monadster”.

The next and final post has a quick summary of the series, and some further reading.