Implementations of the Handle pattern
=====================================

January 31, 2021

In https://blog.ocharles.org.uk/posts/2020-12-23-monad-transformers-and-effects
-with-backpack.html @acid2 presented how to apply Backpack to monad
transformers. There is a less-popular approach to deal with effects — so
called Handle or Service pattern.

In this post, I want to show different implementations of the Handle pattern
and compare them. All examples described below are available in this repository
https://git.ak3n.com/?p=handle-examples.git.

When you might need the Handle pattern
--------------------------------------

Suppose we have a domain logic with a side effect:

```
module WeatherReporter where

import qualified WeatherProvider

type WeatherReport = String

-- | Domain logic. Usually some pure code that might use mtl, free monads, etc.
createWeatherReport :: WeatherProvider.WeatherData -> WeatherReport
createWeatherReport (WeatherProvider.WeatherData temp) =
  "The current temperature in London is " ++ (show temp)

-- | Domain logic that uses external dependency to get data and process it.
getCurrentWeatherReportInLondon :: IO WeatherReport
getCurrentWeatherReportInLondon = do
  weatherData <- WeatherProvider.getWeatherData "London" "now"
  return $ createWeatherReport weatherData
```

```
module WeatherProvider where

type Temperature = Int
data WeatherData = WeatherData { temperature :: Temperature }

type Location = String
type Day = String

-- | This is some concrete implementation.
-- In this example we return a constant value.
getWeatherData :: Location -> Day -> IO WeatherData
getWeatherData _ _ = return $ WeatherData 30
```

At some point in time, there appeared a need for tests to ensure that the
domain logic is correct. There are different ways to do that:

- integration tests
- stub implementation of the service
- minimize the logic with side effects moving as much as possible to pure
  functions for proper unit testing
- maybe something else

All solutions have their pros and cons and the final choice depends on many
factors — especially on the number of side effects and how they interact with
each other. We are interested in how to achieve the second one with the Handle
pattern.

Simple Handle
-------------

Let's start with a simple Handle that doesn't support multiple implementations.
Here is the updated domain logic:

```
module WeatherReporter where

import qualified WeatherProvider

type WeatherReport = String

-- | We hide dependencies in the handle
data Handle = Handle { weatherProvider :: WeatherProvider.Handle }

-- | Constructor for Handle
new :: WeatherProvider.Handle -> Handle
new = Handle

-- | Domain logic. Usually some pure code that might use mtl, free monads, etc.
createWeatherReport :: WeatherProvider.WeatherData -> WeatherReport
createWeatherReport (WeatherProvider.WeatherData temp) =
  "The current temperature in London is " ++ (show temp)

-- | Domain logic that uses external dependency to get data and process it.
getCurrentWeatherReportInLondon :: Handle -> IO WeatherReport
getCurrentWeatherReportInLondon (Handle wph) = do
  weatherData <- WeatherProvider.getWeatherData wph "London" "now"
  return $ createWeatherReport weatherData

```

And the implementation of the `WeatherProvider`:

```
module WeatherProvider where

type Temperature = Int
data WeatherData = WeatherData { temperature :: Temperature }

type Location = String
type Day = String

-- | Our Handle is empty, but usually other dependencies are stored here
data Handle = Handle

-- | Constructor for Handle
new :: Handle
new = Handle

-- | This is some concrete implementation.
-- In this example we return a constant value.
getWeatherData :: Handle -> Location -> Day -> IO WeatherData
getWeatherData _ _ _ = return $ WeatherData 30
```

We have wrapped our service with the Handle interface. It's not possible to
have multiple implementations yet, but we got an interface of the service and
can hide all the dependencies of the service into Handle.

Handle with records
-------------------

The approach with records is described in the aforementioned posts. Records are
used as a dictionary with functions just like dictionary passing with type
classes but explicitly. `WeatherReporter` module stays the same — it continues
to use `WeatherProvider.Handle` while the `WeatherProvider` becomes an interface:

```
module WeatherProvider where

type Temperature = Int
data WeatherData = WeatherData { temperature :: Temperature }

type Location = String
type Day = String

-- | The interface of `WeatherProvider` with available methods.
data Handle = Handle { getWeatherData :: Location -> Day -> IO WeatherData }
```

The good thing is that we do not need to change our domain logic at all since
`getWeatherData :: Handle -> Location -> Day -> IO WeatherData` has the same
type. The interface allows us to create a concrete implementation for the
application:

```
module SuperWeatherProvider where

import WeatherProvider

new :: Handle
new = Handle { getWeatherData = getSuperWeatherData }

-- | This is some concrete implementation `WeatherProvider` interface
getSuperWeatherData :: Location -> Day -> IO WeatherData
getSuperWeatherData _ _ = return $ WeatherData 30
```

And the stub for testing that we can control:

```
module TestWeatherProvider where

import WeatherProvider

-- | This is a configuration that allows to setup the provider for tests.
data Config = Config { initTemperature :: Temperature }

new :: Config -> Handle
new config = Handle
  { getWeatherData = getTestWeatherData $ initTemperature config }

-- | This is an implementation `WeatherProvider` interface for tests
getTestWeatherData :: Int -> Location -> Day -> IO WeatherData
getTestWeatherData temp _ _ = return $ WeatherData temp
```

The downside of this approach is the cost of an unknown function call mentioned
in https://www.schoolofhaskell.com/user/meiersi/the-service-pattern post:

> In terms of call overhead, we pay the cost of an unknown function call, which
is probably a bit slower than a virtual method invocation in an OOP langauge
like Java. If this becomes a performance bottleneck, we will have to avoid the
abstraction and specialize at compile time. Backpack will allow us to do this
in a principled fashion without losing modularity.

Here is the STG of `WeatherReporter`:

```
weatherProvider =
    \r [ds_s1C4] case ds_s1C4 of { Handle ds1_s1C6 -> ds1_s1C6; };

new = \r [eta_B1] Handle [eta_B1];

createWeatherReport1 = "The current temperature in London is "#;

$wcreateWeatherReport =
    \r [ww_s1C7]
        let {
          sat_s1Cd =
              \u []
                  case ww_s1C7 of {
                    I# ww3_s1C9 ->
                        case $wshowSignedInt 0# ww3_s1C9 [] of {
                          (#,#) ww5_s1Cb ww6_s1Cc -> : [ww5_s1Cb ww6_s1Cc];
                        };
                  };
        } in  unpackAppendCString# createWeatherReport1 sat_s1Cd;

createWeatherReport =
    \r [w_s1Ce]
        case w_s1Ce of {
          WeatherData ww1_s1Cg -> $wcreateWeatherReport ww1_s1Cg;
        };

getCurrentWeatherReportInLondon5 = "London"#;

getCurrentWeatherReportInLondon4 =
    \u [] unpackCString# getCurrentWeatherReportInLondon5;

getCurrentWeatherReportInLondon3 = "now"#;

getCurrentWeatherReportInLondon2 =
    \u [] unpackCString# getCurrentWeatherReportInLondon3;

getCurrentWeatherReportInLondon1 =
    \r [ds_s1Ch void_0E]
        case ds_s1Ch of {
          Handle wph_s1Ck ->
              case wph_s1Ck of {
                Handle ds1_s1Cm ->
                    case
                        ds1_s1Cm
                            getCurrentWeatherReportInLondon4
                            getCurrentWeatherReportInLondon2
                            void#
                    of
                    { Unit# ipv1_s1Cp ->
                          let { sat_s1Cq = \u [] createWeatherReport ipv1_s1Cp;
                          } in  Unit# [sat_s1Cq];
                    };
              };
        };

getCurrentWeatherReportInLondon =
    \r [eta_B2 void_0E] getCurrentWeatherReportInLondon1 eta_B2 void#;

Handle = \r [eta_B1] Handle [eta_B1];
```

`getCurrentWeatherReportInLondon` takes two arguments — the first one is
`WeatherReporter`'s Handle dictionary which we pass to
`getCurrentWeatherReportInLondon1`. We match on this dictionary to get
`wph_s1Ck` — this is our `WeatherProvider`'s Handle. Matching on it we get
`ds1_s1Cm` — `getWeatherData` function which is called with arguments:
`getCurrentWeatherReportInLondon4 = "London"` and
`getCurrentWeatherReportInLondon2 = "now"`.

The result of `getWeatherData "London" "now" = ipv1_s1Cp` is then passed to
`createWeatherReport` where we show the result in `$wcreateWeatherReport` and
append it to `createWeatherReport1 = "The current temperature in London is "`.

The STG looks as expected. There are two allocations: one in
`getCurrentWeatherReportInLondon1` and the other one in `$wcreateWeatherReport`.

Handle with Backpack
--------------------

`WeatherProvider` becomes a signature in the cabal file:

```
library domain
  hs-source-dirs: domain
  signatures:      WeatherProvider
  exposed-modules: WeatherReporter
  default-language: Haskell2010
  build-depends:    base
```

and we rename `WeatherProvider.hs` to `WeatherProvider.hsig` with a little
change — instead of using a concrete type for `Temperature` we make it
abstract and will instantiate in implementations.

```
signature WeatherProvider where

data Temperature
instance Show Temperature

data WeatherData = WeatherData { temperature :: Temperature }

type Location = String
type Day = String

data Handle

-- | The interface of `WeatherProvider` with available methods.
getWeatherData :: Handle -> Location -> Day -> IO WeatherData
```

Our implementation module is almost the same as in the simple Handle case, but
we have to follow the signature and export the same types. It's possible to
move all common types to a different cabal library and import in the signature
and the implementations.

```
module SuperWeatherProvider where

type Temperature = Int
data WeatherData = WeatherData { temperature :: Temperature }

type Location = String
type Day = String

-- | Our Handle is empty, but usually other dependencies are stored here
data Handle = Handle

-- | Constructor for Handle
new :: Handle
new = Handle

-- | This is some concrete implementation.
-- In this example we return a constant value.
getWeatherData :: Handle -> Location -> Day -> IO WeatherData
getWeatherData _ _ _ = return $ WeatherData 30
```

For tests we setup a configuration type to control the behavior:

```haskell
module TestWeatherProvider where

type Temperature = Int
data WeatherData = WeatherData { temperature :: Temperature }

type Location = String
type Day = String

-- | This is a configuration that allows to setup the provider for tests.
data Config = Config { initTemperature :: Temperature }

data Handle = Handle { config :: Config }

new :: Config -> Handle
new = Handle

-- | This is an implementation `WeatherProvider` interface for tests
getWeatherData :: Handle -> Location -> Day -> IO WeatherData
getWeatherData (Handle conf) _ _ = return $ WeatherData $ initTemperature conf
```

Now we need to tell which module to use instead of `WeatherProvider` hole.
There are two ways: by using mixins or by reexporting modules as
`WeatherProvider` in the definition libraries:

```
library impl
  hs-source-dirs: impl
  exposed-modules: SuperWeatherProvider
  reexported-modules: SuperWeatherProvider as WeatherProvider
  default-language: Haskell2010
  build-depends: base

library test-impl
  hs-source-dirs: test-impl
  exposed-modules: TestWeatherProvider
  reexported-modules: TestWeatherProvider as WeatherProvider
  default-language: Haskell2010
  build-depends: base

executable backpack-handle-exe
  main-is: Main.hs
  build-depends: base, impl, domain
  default-language: Haskell2010

test-suite spec
  type: exitcode-stdio-1.0
  hs-source-dirs: test
  main-is: Test.hs
  default-language: Haskell2010
  build-depends: base, QuickCheck, hspec, domain, test-impl
```

That's all. We do not need to change our domain logic or tests. Here is the STG
of `WeatherReporter`:

```
weatherProvider =
    \r [ds_s1Bq] case ds_s1Bq of { Handle ds1_s1Bs -> ds1_s1Bs; };

new = \r [eta_B1] Handle [eta_B1];

createWeatherReport1 = "The current temperature in London is "#;

$wcreateWeatherReport =
    \r [ww_s1Bt]
        let {
          sat_s1Bz =
              \u []
                  case ww_s1Bt of {
                    I# ww3_s1Bv ->
                        case $wshowSignedInt 0# ww3_s1Bv [] of {
                          (#,#) ww5_s1Bx ww6_s1By -> : [ww5_s1Bx ww6_s1By];
                        };
                  };
        } in  unpackAppendCString# createWeatherReport1 sat_s1Bz;

createWeatherReport =
    \r [w_s1BA]
        case w_s1BA of {
          WeatherData ww1_s1BC -> $wcreateWeatherReport ww1_s1BC;
        };

getCurrentWeatherReportInLondon3 =
    \u []
        case $wshowSignedInt 0# 30# [] of {
          (#,#) ww5_s1BE ww6_s1BF -> : [ww5_s1BE ww6_s1BF];
        };

getCurrentWeatherReportInLondon2 =
    \u []
        unpackAppendCString#
            createWeatherReport1 getCurrentWeatherReportInLondon3;

getCurrentWeatherReportInLondon1 =
    \r [ds_s1BG void_0E]
        case ds_s1BG of {
          Handle _ -> Unit# [getCurrentWeatherReportInLondon2];
        };

getCurrentWeatherReportInLondon =
    \r [eta_B2 void_0E] getCurrentWeatherReportInLondon1 eta_B2 void#;

Handle = \r [eta_B1] Handle [eta_B1];
```

We can see that GHC inlined our constant implementation in the
`getCurrentWeatherReportInLondon3` — we show `30` immediately.

Handles with Backpack
---------------------

I decided to go further and make `WeatherReporter` a signature as well. Turned
out this step required more actions with libraries. Here is
`WeatherReporter.hsig`:

```
signature WeatherReporter where

import qualified WeatherProvider

type WeatherReport = String

data Handle = Handle { weatherProvider :: WeatherProvider.Handle }

-- | This is domain logic. It uses `WeatherProvider` to get the actual data.
getCurrentWeatherReportInLondon :: Handle -> IO WeatherReport
```

Then we need to split the `domain` library into two because `WeatherReport`
depends on `WeatherProvider`. I tried to implement them both with one
implementation library but seems it's impossible. The structure of libraries
becomes the following:

```
library domain-provider
  hs-source-dirs: domain
  signatures:      WeatherProvider
  default-language: Haskell2010
  build-depends:    base

library domain-reporter
  hs-source-dirs: domain
  signatures:      WeatherReporter
  default-language: Haskell2010
  build-depends:    base, domain-provider

library impl-provider
  hs-source-dirs: impl
  exposed-modules: SuperWeatherProvider
  reexported-modules: SuperWeatherProvider as WeatherProvider
  default-language: Haskell2010
  build-depends:    base

library impl-reporter
  hs-source-dirs: impl
  exposed-modules: SuperWeatherReporter
  reexported-modules: SuperWeatherReporter as WeatherReporter
  default-language: Haskell2010
  build-depends:    base, domain-provider
```

Instead of `domain` we have `domain-provider` and `domain-reporter`. It allows
to depend on them individually and instantiate with different implementations.
In the example I have instantiated the provider with implementation from
`test-impl` using the reporter from `impl-reporter`. This is useful if you want
to gradually write tests for different parts of the logic.

Handle with Vinyl
-----------------

Suppose that we want to extend our `WeatherData` and return not only temperature
but wind's speed too. We need to add a field to `WeatherData`:

```
data WeatherData =
  WeatherData { temperature :: T.Temperature, wind :: W.WindSpeed }
```

But also we want to provide separate Handles for these values:
`TemperatureProvider` and `WindProvider`. We can create these two providers and
then duplicate their methods in `WeatherProvider`. That might work if there are
no so many methods, but what if their number will grow?

We know that records are nominally typed and can't be composed. There is a
library called https://hackage.haskell.org/package/vinyl that provides
structural records supporting merge operation. I recommend Jon Sterling's talk
on Vinyl https://vimeo.com/102785458 where you can learn why records are
sheaves and other details on Vinyl.

Let's explore what the Handle pattern will look like if we replace records
with Vinyl records. We create our data providers `TemperatureProvider` and
`WindProvider`:

```
module TemperatureProvider where

import HandleRec
import QueryTypes

type Temperature = Int

type Methods = '[ '("getTemperatureData", (Location -> Day -> IO Temperature)) ]

type Handle = HandleRec Methods

getTemperatureData :: Handle -> Location -> Day -> IO Temperature
getTemperatureData = getMethod @"getTemperatureData"
```

```
module WindProvider where

import HandleRec
import QueryTypes

type WindSpeed = Int

type Methods = '[ '("getWindData", (Location -> Day -> IO WindSpeed)) ]

type Handle = HandleRec Methods

getWindData :: Handle -> Location -> Day -> IO WindSpeed
getWindData = getMethod @"getWindData"
```

Note that we provide `getTemperatureData` and `getWindData` functions which
satisfy our Handle interface.

We can compose them together and extend them to create `WeatherProvider`:

```
module WeatherProvider where

import Data.Vinyl.TypeLevel
import HandleRec
import qualified WindProvider as W
import qualified TemperatureProvider as T
import QueryTypes

data WeatherData =
  WeatherData { temperature :: T.Temperature, wind :: W.WindSpeed }

-- We union the methods of providers and extend it with a common method.
type Methods = '[ '("getWeatherData", (Location -> Day -> IO WeatherData))
  ] ++ W.Methods ++ T.Methods

type Handle = HandleRec Methods

getWeatherData :: Handle -> Location -> Day -> IO WeatherData
getWeatherData = getMethod @"getWeatherData"
```

Here is how our providers are implemented:

```
module SuperTemperatureProvider where

import Data.Vinyl
import TemperatureProvider
import QueryTypes

new :: Handle
new = Field getSuperTemperatureData :& RNil

getSuperTemperatureData :: Location -> Day -> IO Temperature
getSuperTemperatureData _ _ = return 30
```

```haskell
module SuperWindProvider where

import Data.Vinyl
import WindProvider
import QueryTypes

new :: Handle
new = Field getSuperWindData :& RNil

getSuperWindData :: Location -> Day -> IO WindSpeed
getSuperWindData _ _ = return 5
```

```
module SuperWeatherProvider where

import Data.Vinyl
import WeatherProvider
import qualified TemperatureProvider
import qualified WindProvider
import QueryTypes

new :: WindProvider.Handle -> TemperatureProvider.Handle -> Handle
new wp tp = Field getSuperWeatherData :& RNil <+> wp <+> tp

-- | This is some concrete implementation `WeatherProvider` interface
getSuperWeatherData :: Location -> Day -> IO WeatherData
getSuperWeatherData _ _ = return $ WeatherData 30 10
```

The domain logic and tests stay the same thanks to the Handle interface. The
STG of `WeatherReporter`:

```
createWeatherReport2 = "The current temperature in London is "#;

createWeatherReport1 = " and wind speed is "#;

$wcreateWeatherReport =
  \r [ww_s2fz ww1_s2fA]
    let {
      sat_s2fN =
        \u []
          case ww_s2fz of {
            I# ww3_s2fC ->
              case $wshowSignedInt 0# ww3_s2fC [] of {
                (#,#) ww5_s2fE ww6_s2fF ->
                  let {
                    sat_s2fM =
                      \s []
                        let {
                          sat_s2fL =
                            \u []
                              case ww1_s2fA of {
                                I# ww9_s2fH ->
                                  case $wshowSignedInt 0# ww9_s2fH [] of {
                                    (#,#) ww11_s2fJ ww12_s2fK ->
                                        : [ww11_s2fJ ww12_s2fK];
                                  };
                              };
                        } in  unpackAppendCString# createWeatherReport1 sat_s2fL;
                  } in  ++_$s++ sat_s2fM ww5_s2fE ww6_s2fF;
              };
          };
    } in  unpackAppendCString# createWeatherReport2 sat_s2fN;

createWeatherReport =
    \r [w_s2fO]
        case w_s2fO of {
          WeatherData ww1_s2fQ ww2_s2fR ->
              $wcreateWeatherReport ww1_s2fQ ww2_s2fR;
        };

getCurrentWeatherReportInLondon5 = "London"#;

getCurrentWeatherReportInLondon4 =
    \u [] unpackCString# getCurrentWeatherReportInLondon5;

getCurrentWeatherReportInLondon3 = "now"#;

getCurrentWeatherReportInLondon2 =
    \u [] unpackCString# getCurrentWeatherReportInLondon3;

getCurrentWeatherReportInLondon1 =
    \r [ds_s2fS void_0E]
        case ds_s2fS of {
          Handle wph_s2fV ->
            case wph_s2fV of {
              :& x1_s2fX _ ->
                case x1_s2fX of {
                  Field _ x2_s2g1 ->
                    case
                        x2_s2g1
                            getCurrentWeatherReportInLondon4
                            getCurrentWeatherReportInLondon2
                            void#
                    of
                    { Unit# ipv1_s2g4 ->
                          let { sat_s2g5 = \u [] createWeatherReport ipv1_s2g4;
                          } in  Unit# [sat_s2g5];
                    };
                };
            };
        };

getCurrentWeatherReportInLondon =
    \r [eta_B2 void_0E] getCurrentWeatherReportInLondon1 eta_B2 void#;

Handle = \r [eta_B1] Handle [eta_B1];
```

As we can see there is not much vinyl-specific runtime overhead in this case —
we pattern match on `wph_s2fV` and `x1_s2fX` to get the function `x2_s2g1`. But
keep in mind that accessing an element is linear since Vinyl is based on HList
and compilation time will grow because of type-level machinery. Vinyl can be
replaced for an alternative with logarithmic complexity.

Conclusions
-----------

No surprises here. Backpack works as expected specifying things at compile time.
Vinyl allows to compose records and can be replaced with any alternative. The
Handle pattern works since it's just a type signature  `... :: Handle -> ...`.

The Handle allows us to hide dependencies and to create interfaces, allowing us
to easily replace the implementation without changes on the client-side —
statically using Backpack for better performance or dynamically using records
or alternatives in runtime (in first-class modules manner). Backpack might be
too tedious for Handles that depend on each other but in simple cases, it
introduces not much additional cost compared to records. And it's possible to
mix them.