Reinventing the Reader monad

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:

Open API connection
Get product ids purchased by customer id using the API
For each product id:
    get the product info for that id using the API
Close API connection
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:

type CustId = CustId of string
type ProductId = ProductId of string
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.
type ApiClient() =
    // static storage
    static let mutable data = Map.empty<string,obj>

    /// Try casting a value
    /// Return Success of the value or Failure on failure
    member private this.TryCast<'a> key (value:obj) =
        match value with
        | :? 'a as a ->
            Result.Success a 
        | _  ->                 
            let typeName = typeof<'a>.Name
            Result.Failure [sprintf "Can't cast value at %s to %s" key typeName]

    /// Get a value
    member this.Get<'a> (id:obj) = 
        let key =  sprintf "%A" id
        printfn "[API] Get %s" key
        match Map.tryFind key data with
        | Some o -> 
            this.TryCast<'a> key o
        | None -> 
            Result.Failure [sprintf "Key %s not found" key]

    /// Set a value
    member this.Set (id:obj) (value:obj) = 
        let key =  sprintf "%A" id
        printfn "[API] Set %s" key
        if key = "bad" then  // for testing failure paths
            Result.Failure [sprintf "Bad Key %s " key]
        else
            data <- Map.add key value data 
            Result.Success ()

    member this.Open() =
        printfn "[API] Opening"

    member this.Close() =
        printfn "[API] Closing"

    interface System.IDisposable with
        member this.Dispose() =
            printfn "[API] Disposing"

Let's do some tests:

do
    use api = new ApiClient()
    api.Get "K1" |> printfn "[K1] %A"

    api.Set "K2" "hello" |> ignore
    api.Get<string> "K2" |> printfn "[K2] %A"

    api.Set "K3" "hello" |> ignore
    api.Get<int> "K3" |> printfn "[K3] %A"

And the results are:

[API] Get "K1"
[K1] Failure ["Key "K1" not found"]
[API] Set "K2"
[API] Get "K2"
[K2] Success "hello"
[API] Set "K3"
[API] Get "K3"
[K3] Failure ["Can't cast value at "K3" to Int32"]
[API] Disposing


A first implementation attempt

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

let getPurchaseInfo (custId:CustId) : Result<ProductInfo list> =

    // Open api connection       
    use api = new ApiClient()
    api.Open()

    // Get product ids purchased by customer id
    let productIdsResult = api.Get<ProductId list> custId

    let productInfosResult = ??

    // Close api connection
    api.Close()

    // Return the list of product infos
    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.

let getPurchaseInfo (custId:CustId) : Result<ProductInfo list> =

    // Open api connection       
    use api = new ApiClient()
    api.Open()

    // Get product ids purchased by customer id
    let productIdsResult = api.Get<ProductId list> custId

    let productInfosResult =
        match productIdsResult with
        | Success productIds -> 
            let productInfos = ResizeArray()  // Same as .NET List<T>
            for productId in productIds do
                let productInfo = api.Get<ProductInfo> productId
                productInfos.Add productInfo  // mutation! 
            Success productInfos
        | Failure err ->    
            Failure err 

    // Close api connection
    api.Close()

    // Return the list of product infos
    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.

let getPurchaseInfo (custId:CustId) : Result<ProductInfo list> =

    // Open api connection       
    use api = new ApiClient()
    api.Open()

    // Get product ids purchased by customer id
    let productIdsResult = api.Get<ProductId list> custId

    let productInfosResult =
        match productIdsResult with
        | Success productIds -> 
            let productInfos = ResizeArray()  // Same as .NET List<T>
            let mutable anyFailures = false
            for productId in productIds do
                let productInfoResult = api.Get<ProductInfo> productId
                match productInfoResult with
                | Success productInfo ->
                    productInfos.Add productInfo 
                | Failure err ->    
                    Failure err 
            Success productInfos
        | Failure err ->    
            Failure err 

    // Close api connection
    api.Close()

    // Return the list of product infos
    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:

/// CustId -> Result<ProductInfo list>
let getPurchaseInfo (custId:CustId) : Result<ProductInfo list> =

    // Open api connection       
    use api = new ApiClient()
    api.Open()

    let productInfosResult = Result.result {

        // Get product ids purchased by customer id
        let! productIds = api.Get<ProductId list> custId

        let productInfos = ResizeArray()  // Same as .NET List<T>
        for productId in productIds do
            let! productInfo = api.Get<ProductInfo> productId
            productInfos.Add productInfo 
        return productInfos |> List.ofSeq
        }

    // Close api connection
    api.Close()

    // Return the list of product infos
    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.

module Result = 

    let bind f xResult = ...

    type ResultBuilder() =
        member this.Return x = retn x
        member this.ReturnFrom(m: Result<'T>) = m
        member this.Bind(x,f) = bind f x

        member this.Zero() = Failure []
        member this.Combine (x,f) = bind f x
        member this.Delay(f: unit -> _) = f
        member this.Run(f) = f()

        member this.TryFinally(m, compensation) =
            try this.ReturnFrom(m)
            finally compensation()

        member this.Using(res:#System.IDisposable, body) =
            this.TryFinally(body res, fun () -> 
            match res with 
            | null -> () 
            | disp -> disp.Dispose())

        member this.While(guard, f) =
            if not (guard()) then 
                this.Zero() 
            else
                this.Bind(f(), fun _ -> this.While(guard, f))

        member this.For(sequence:seq<_>, body) =
            this.Using(sequence.GetEnumerator(), fun enum -> 
                this.While(enum.MoveNext, this.Delay(fun () -> 
                    body enum.Current)))

    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.

let executeApiAction apiAction  =

    // Open api connection       
    use api = new ApiClient()
    api.Open()

    // do something with it
    let result = apiAction api

    // Close api connection
    api.Close()

    // return result
    result

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

/// CustId -> ApiClient -> Result<ProductInfo list>
let getPurchaseInfo (custId:CustId) (api:ApiClient) =

    let productInfosResult = Result.result {
        let! productIds = api.Get<ProductId list> custId

        let productInfos = ResizeArray()  // Same as .NET List<T>
        for productId in productIds do
            let! productInfo = api.Get<ProductInfo> productId
            productInfos.Add productInfo 
        return productInfos |> List.ofSeq
        }

    // return result
    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:

let action = getPurchaseInfo (CustId "C1")  // partially apply
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:

/// CustId -> ApiClient -> Result<ProductId list>
let getPurchaseIds (custId:CustId) (api:ApiClient) =
    api.Get<ProductId list> custId

/// ProductId -> ApiClient -> Result<ProductInfo>
let getProductInfo (productId:ProductId) (api:ApiClient) =
    api.Get<ProductInfo> productId

/// CustId -> ApiClient -> Result<ProductInfo list>
let getPurchaseInfo (custId:CustId) (api:ApiClient) =

    let result = Result.result {
        let! productIds = getPurchaseIds custId api 

        let productInfos = ResizeArray()  
        for productId in productIds do
            let! productInfo = getProductInfo productId api
            productInfos.Add productInfo 
        return productInfos |> List.ofSeq
        }

    // return result
    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:

let getPurchaseInfo (custId:CustId) =
    custId 
    |> getPurchaseIds 
    |> List.map getProductInfo

Or as a diagram:

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:

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

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.

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.

In fact, let's do more than that -- let's make it a type!

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.

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:

// CustId -> ApiAction<Result<ProductId list>>
let getPurchaseIds (custId:CustId) =

    // create the api-consuming function
    let action (api:ApiClient) = 
        api.Get<ProductId list> custId

    // wrap it in the single case
    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:

// ProductId -> ApiAction<Result<ProductInfo>>
let getProductInfo (productId:ProductId) =

    // create the api-consuming function
    let action (api:ApiClient) = 
        api.Get<ProductInfo> productId

    // wrap it in the single case
    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>>>:

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

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

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:

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:

Enough diagrams -- let's write some code now.

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

module ApiAction = 

    /// Evaluate the action with a given api
    /// ApiClient -> ApiAction<'a> -> 'a
    let run api (ApiAction action) = 
        let resultOfAction = action api
        resultOfAction

    /// ('a -> 'b) -> ApiAction<'a> -> ApiAction<'b>
    let map f action = 
        let newAction api =
            let x = run api action 
            f x
        ApiAction newAction

    /// 'a -> ApiAction<'a>
    let retn x = 
        let newAction api =
            x
        ApiAction newAction

    /// ApiAction<('a -> 'b)> -> ApiAction<'a> -> ApiAction<'b>
    let apply fAction xAction = 
        let newAction api =
            let f = run api fAction 
            let x = run api xAction 
            f x
        ApiAction newAction

    /// ('a -> ApiAction<'b>) -> ApiAction<'a> -> ApiAction<'b>
    let bind f xAction = 
        let newAction api =
            let x = run api xAction 
            run api (f x)
        ApiAction newAction

    /// Create an ApiClient and run the action on it
    /// ApiAction<'a> -> 'a
    let execute action =
        use api = new ApiClient()
        api.Open()
        let result = run api action
        api.Close()
        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:

module ApiActionResult = 

    let map f  = 
        ApiAction.map (Result.map f)

    let retn x = 
        ApiAction.retn (Result.retn x)

    let apply fActionResult xActionResult = 
        let newAction api =
            let fResult = ApiAction.run api fActionResult 
            let xResult = ApiAction.run api xActionResult 
            Result.apply fResult xResult 
        ApiAction newAction

    let bind f xActionResult = 
        let newAction api =
            let xResult = ApiAction.run api xActionResult 
            // create a new action based on what xResult is
            let yAction = 
                match xResult with
                | Success x -> 
                    // Success? Run the function
                    f x
                | Failure err -> 
                    // Failure? wrap the error in an ApiAction
                    (Failure err) |> ApiAction.retn
            ApiAction.run api yAction  
        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.

Map

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

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

Here's our getProductInfo before transformation:

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

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.

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

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.

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

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.

And here it is expressed as code:

let getPurchaseInfo =
    let getProductInfo1 = traverse getProductInfo
    let getProductInfo2 = ApiActionResult.bind getProductInfo1 
    getPurchaseIds >> getProductInfo2

Or to make it a bit less ugly:

let getPurchaseInfo =
    let getProductInfoLifted =
        getProductInfo
        |> traverse 
        |> ApiActionResult.bind 
    getPurchaseIds >> getProductInfoLifted

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

let getPurchaseInfo (custId:CustId) (api:ApiClient) =

    let result = Result.result {
        let! productIds = getPurchaseIds custId api 

        let productInfos = ResizeArray()  
        for productId in productIds do
            let! productInfo = getProductInfo productId api
            productInfos.Add productInfo 
        return productInfos |> List.ofSeq
        }

    // return result
    result

Let's compare the two versions in a table:

Earlier verson Latest function
Composite function is non-trivial and needs special code to glue the two smaller functions together Composite 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:

let traverse f list =
    // define the applicative functions
    let (<*>) = ApiActionResult.apply
    let retn = ApiActionResult.retn

    // define a "cons" function
    let cons head tail = head :: tail

    // right fold over the list
    let initState = retn []
    let folder head tail = 
        retn cons <*> f head <*> tail

    List.foldBack folder list initState

Testing the implementation

Let's test it!

First we need a helper function to show results:

let showResult result =
    match result with
    | Success (productInfoList) -> 
        printfn "SUCCESS: %A" productInfoList
    | Failure errs -> 
        printfn "FAILURE: %A" errs

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

let setupTestData (api:ApiClient) =
    //setup purchases
    api.Set (CustId "C1") [ProductId "P1"; ProductId "P2"] |> ignore
    api.Set (CustId "C2") [ProductId "PX"; ProductId "P2"] |> ignore

    //setup product info
    api.Set (ProductId "P1") {ProductName="P1-Name"} |> ignore
    api.Set (ProductId "P2") {ProductName="P2-Name"} |> ignore
    // P3 missing

// setupTestData is an api-consuming function
// so it can be put in an ApiAction 
// and then that apiAction can be executed
let setupAction = ApiAction setupTestData
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:

CustId "C1"
|> getPurchaseInfo
|> ApiAction.execute
|> showResult

And here are the results:

[API] Opening
[API] Get CustId "C1"
[API] Get ProductId "P1"
[API] Get ProductId "P2"
[API] Closing
[API] Disposing
SUCCESS: [{ProductName = "P1-Name";}; {ProductName = "P2-Name";}]

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

CustId "CX"
|> getPurchaseInfo
|> ApiAction.execute
|> 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.

[API] Opening
[API] Get CustId "CX"
[API] Closing
[API] Disposing
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.

CustId "C2"
|> getPurchaseInfo
|> ApiAction.execute
|> showResult

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

[API] Opening
[API] Get CustId "C2"
[API] Get ProductId "PX"
[API] Get ProductId "P2"
[API] Closing
[API] Disposing
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:

module ApiActionResult = 

    let map = ...
    let retn =  ...
    let apply = ...
    let bind = ...

    let either onSuccess onFailure xActionResult = 
        let newAction api =
            let xResult = ApiAction.run api xActionResult 
            let yAction = 
                match xResult with
                | Result.Success x -> onSuccess x 
                | Result.Failure err -> onFailure err
            ApiAction.run api yAction  
        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:

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

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

let traverseWithLog log f list =
    // define the applicative functions
    let (<*>) = ApiActionResult.apply
    let retn = ApiActionResult.retn

    // define a "cons" function
    let cons head tail = head :: tail

    // right fold over the list
    let initState = retn []
    let folder head tail = 
        (f head) 
        |> ApiActionResult.either 
            (fun h -> retn cons <*> retn h <*> tail)
            (fun errs -> log errs; tail)
    List.foldBack folder list initState

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

let folder head tail = 
    (f head) 
    |> ApiActionResult.either 
        (fun h -> retn cons <*> retn h <*> tail)
        (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:

let getPurchasesInfoWithLog =
    let log errs = printfn "SKIPPED %A" errs 
    let getProductInfoLifted =
        getProductInfo 
        |> traverseWithLog log 
        |> ApiActionResult.bind 
    getPurchaseIds >> getProductInfoLifted

CustId "C2"
|> getPurchasesInfoWithLog
|> ApiAction.execute
|> showResult

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

[API] Opening
[API] Get CustId "C2"
[API] Get ProductId "PX"
SKIPPED ["Key ProductId "PX" not found"]
[API] Get ProductId "P2"
[API] Closing
[API] Disposing
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:

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:

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:

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!

module Reader = 

    /// Evaluate the action with a given environment
    /// 'env -> Reader<'env,'a> -> 'a
    let run environment (Reader action) = 
        let resultOfAction = action environment
        resultOfAction

    /// ('a -> 'b) -> Reader<'env,'a> -> Reader<'env,'b>
    let map f action = 
        let newAction environment =
            let x = run environment action 
            f x
        Reader newAction

    /// 'a -> Reader<'env,'a>
    let retn x = 
        let newAction environment =
            x
        Reader newAction

    /// Reader<'env,('a -> 'b)> -> Reader<'env,'a> -> Reader<'env,'b>
    let apply fAction xAction = 
        let newAction environment =
            let f = run environment fAction 
            let x = run environment xAction 
            f x
        Reader newAction

    /// ('a -> Reader<'env,'b>) -> Reader<'env,'a> -> Reader<'env,'b>
    let bind f xAction = 
        let newAction environment =
            let x = run environment xAction 
            run environment (f x)
        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.

results matching ""

    No results matching ""