Metapolymorphic providers
Tags: webdev, functional
Published: 2022-04-29 10:16:16

---
title: Metapolymorphic providers
published: true
description: Horns of plenty in web apps of great fortune
tags: webdev, functional
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1g0m7yrouohhcem1gb6l.jpg
---

In a recent article [Horizontal and Vertical Events](https://dev.to/mikesol/horizontal-and-vertical-events-1pm1), I talked about some of the ways that events can be pushed to "horizontal" and "vertical" boundaries of an application, where **horizontal** spreads an event across a component and **vertical** pushes an event into nested components.

In the article, I mentioned briefly that we can use `Event`-s to create a provider system in our application. In this article I'd like to unpack that assertion. I'll start by defining what I mean by provider and then show how it can be implemented using something I call "metapolymorphism" or "wild-card polymorphism."

## Providers 101

When I say "provider", I'm borrowing a term from [React](https://reactjs.org/docs/context.html). In React, one can create contexts, and then use providers to get values from the context in any sub-element. This is absolutely essential when working with things like authorization tokens: we do not want to write logic to constantly push tokens layers deep down an application. Instead, we want to pick them off where we need them and, importantly, have the compiler complain if they are of the wrong type or if they are not provided.

A good provider system needs to fulfill the following criteria:

1. One should be able to get information from the provider at _any_ level of the application.
2. The machinery to push provided values down through an application should be minimalistic, boring and consistent.
3. One should be able to create _ad hoc_ providers anywhere in an application, compose providers, and modify their values on the fly.
4. One should be able to mute providers: that is, turn them off for downstream consumers that do not need them. React _doesn't_ do this, but I think it is essential: we don't want third-party components sneakily introspecting our providers!
5. One should be able to interact with the provider, for example to trigger the refreshing of a token if needed.

## A minimal provider application

Here's an application that fulfills all four criteria. It's written using [PureScript Deku](https://github.com/mikesol/purescript-deku), a small library I created to experiment with event-based architectures in the DOM. The application provisions two tokens, propagating them down through the DOM using providers. It also provides mechanisms to refresh and invalidate our tokens, sending us back to a log-in page.

I'll copy-and-paste the application below, and you can (and should!) try it out via the **Try it here** link. Once you play with it and see from interaction how it caches, refreshes, and invalidates tokens, you'll be in a good spot to read the rest of the article.

```haskell
data TokenState = Valid | Refreshed Number | Invalid

topMatter :: forall l p. Element l p
topMatter = D.div_
  [ D.p_
      [ text_
          "In the example below, when you click on the buttons needing a token, one of three things will happen. It will either:"
      ]
  , D.ul_
      ( map (D.li_ <<< text_)
          [ "keep the same token;", "get a new token; or", "log you out." ]
      )
  , D.p_
      [ text_
          "This is how real-world apps work: based on user interaction, you periodically need to get a refresh token and, if the token is not valid, log the person out."
      ]
  ]

cell0 :: forall l p. _ -> Element l p
cell0 = pure (D.td_ $ text_ "I'm just a cell...")

cell1 :: forall l p. { myBool :: Boolean | _ } -> Element l p
cell1 = do
  { myBool } <- ask
  pure $ D.td_ $ text_
    ("Here's a boolean from an internal provider: " <> show myBool)

cell2 :: forall l p. { token1 :: Event Number | _ } -> Element l p
cell2 = do
  t1 <- asks _.token1
  pure (D.td_ $ text (("Token 1: " <> _) <<< show <$> t1))

cell3 :: forall l p. { push1 :: Effect Unit | _ } -> Element l p
cell3 = do
  { push1 } <- ask
  pure
    ( D.td_ $ D.button (bang (D.OnClick := push1))
        (text_ "Do something needing token 1")
    )

cell4 :: forall l p. { token2 :: Event Number | _ } -> Element l p
cell4 = do
  t2 <- asks _.token2
  pure (D.td_ $ text (("Token 2: " <> _) <<< show <$> t2))

cell5 :: forall l p. Effect Unit -> Element l p
cell5 = do
  push2 <- ask
  pure
    ( D.td_ $ D.button (bang (D.OnClick := push2))
        (text_ "Do something needing token 2")
    )

incrementToken
  :: forall l p. Event ({ token1 :: Event Number | _ } -> Domable l p)
incrementToken = bus \setN n -> do
  t1 <- asks _.token1
  let t1n = t1 <|> n
  pure $ plant $ D.div_
    [ D.p_ $ text_ "Increment token 1 locally, leaving it unchanged elsewhere."
    , D.button (map (attr D.OnClick <<< setN <<< add 1.0) t1n) (text_ "Increment")
    , D.p_ [ text_ "Token: ", text (map show t1n) ]
    ]

authorized :: forall l p. _ -> Element l p
authorized = do
  c0 <- cell0
  c1 <- lcmap (union { myBool: true }) cell1
  c2 <- cell2
  c3 <- cell3
  c4 <- cell4
  c5 <- lcmap _.push2 cell5
  incTok <- distribute incrementToken
  pure $ D.div_
    [ topMatter
    , D.table_
        [ D.tr_
            [ D.th_ (text_ "Column 1")
            , D.th_ (text_ "Column 2")
            ]
        , D.tr_ [ c0, c1 ]
        , D.tr_ [ c2, c3 ]
        , D.tr_ [ c4, c5 ]
        ]
    , D.div_ incTok
    ]

unauthorized
  :: forall l p
   . { token1 :: Number -> Effect Unit
     , token2 :: Number -> Effect Unit
     | _
     }
  -> Element l p
unauthorized = do
  { token1, token2 } <- ask
  pure $ D.button
    ( bang $ D.OnClick := do
        getToken >>= token1
        getToken >>= token2
    )
    (text_ "Log in")

type Tokens = V (token1 :: Maybe Number, token2 :: Maybe Number)

keepRefreshOrInvalidate
  :: (Maybe Number -> Effect Unit) -> Effect Unit
keepRefreshOrInvalidate push = do
  tk <- tokenState
  case tk of
    Valid -> mempty
    Refreshed i -> push $ Just i
    Invalid -> push Nothing

main :: Effect Unit
main = runInBody1
  ( vbus (Proxy :: _ Tokens) \push event -> do
      let
        tokens = biSampleOn (bang Nothing <|> event.token2)
          ({ token1: _, token2: _ } <$> (bang Nothing <|> event.token1))
      tokens # switcher \{ token1: t1, token2: t2 } -> case t1, t2 of
        Just t1', Just t2' -> do
          let token1 = compact event.token1 <|> bang t1'
          let token2 = compact event.token2 <|> bang t2'
          let push1 = keepRefreshOrInvalidate push.token1
          let push2 = keepRefreshOrInvalidate push.token2
          authorized { token1, token2, push1, push2 }
        _, _ -> unauthorized
          { token1: lcmap Just push.token1, token2: lcmap Just push.token2 }
  )
```

## What's in a provider?

Providers provide, and nothing provides better than a function. In the example above, our provider is just a function. If you look at the signature for `authorized`, `unauthorized`, `cell0`, `cell1`, etc, you'll see that they're all functions with the exception of `incrementToken` (we'll get to this later). Importantly, _all_ of them use the underscore `_` in their argument. 

I'll get to what the underscore means when I talk about metapolymorphism, but for now, you can think of it as a wild card for a type. Components that need nothing from a provider use `_` as the the argument, and components that need something from a provider declare what they need and use `_` for the rest.

## Getting values from a provider

I said that providers are functions, but looking at all of the examples, they don't look like functions.

Let's take a closer look at `cell4`:

```haskell
cell4 :: forall l p. { token2 :: Event Number | _ } -> Element l p
cell4 = do
  t2 <- asks _.token2
  pure (D.td_ $ text (("Token 2: " <> _) <<< show <$> t2))
```

It leverages the fact that `Function a` implements `MonadAsk` to use `asks` (which gets the input to a function) and also implements `Applicative`), which creates a pure function that ignores its argument. We could have also written `cell4` as: 

```haskell
cell4 :: forall l p. { token2 :: Event Number | _ } -> Element l p
cell4 { token2 } =
  D.td_ $ text (("Token 2: " <> _) <<< show <$> token2)
```

However, to remain consistent in how the app looks and feels, everything uses `do` notation, and in many places, `do` notation is by far the most ergonomic way to work with our functions, aka providers.

If we look at `authorized`, we'll see that it never uses `ask`, and yet it still passes the provided value down to the children `cell0`, `cell1` etc without any extra machinery:

```haskell
authorized :: forall l p. _ -> Element l p
authorized = do
  c0 <- cell0
  c1 <- lcmap (union { myBool: true }) cell1
  c2 <- cell2
  c3 <- cell3
  c4 <- cell4
  c5 <- cell5
  incTok <- distribute incrementToken
  pure $ D.div_
    [ topMatter
    , D.table_
        [ D.tr_
            [ D.th_ (text_ "Column 1")
            , D.th_ (text_ "Column 2")
            ]
        , D.tr_ [ c0, c1 ]
        , D.tr_ [ c2, c3 ]
        , D.tr_ [ c4, c5 ]
        ]
    , D.div_ incTok
    ]
```

This is because `Function a` implements `Apply`, `Applicative`, and `Bind`, which allows us to work with it as a monad using `do` notation and get its argument passed down throughout an application for free.

## _Ad hoc_ modifications to providers

In the `authorized` function above, we see a special modification being done for `cell1`. Let's look at `cell1`'s signature:

```haskell
cell1 :: forall l p. { myBool :: Boolean | _ } -> Element l p
cell1 = do
  { myBool } <- ask
  pure $ D.td_ $ text_
    ("Here's a boolean from an internal provider: " <> show myBool)
```

The provider for `cell1` takes a field `myBool` that is not provided by the top-level provider. To rectify this, we can modify our provider to add a `myBool`, or in other words, create an _ad hoc_ internal provider.

```haskell
  c1 <- lcmap (union { myBool: true }) cell1
```

We can use the same method to mute fields from a provider. Looking at the signature of `cell5`, we see that it only takes a pusher.

```haskell
cell5 :: forall l p. Effect Unit -> Element l p
cell5 = do
  push2 <- ask
  pure
    ( D.td_ $ D.button (bang (D.OnClick := push2))
        (text_ "Do something needing token 2")
    )
```

To "mute" the other arguments of the provider, we can also use `lcmap`.

```haskell
  c5 <- lcmap _.push2 cell5
```

## Pushing a provider down the DOM

So far, we've seen that the monadic `bind` operation and `do` notation will push a provider, aka `Function a`, down the DOM without any additional machinery.

In the world of vertical events, however, this becomes problematic. **Vertical events** is a metaphor I use in [Horizontal and Vertical Events](https://dev.to/mikesol/horizontal-and-vertical-events-1pm1) for things of type `Event (Event (... n))`, where many events are deeply nested. This can include `Free Event n`, `Nu Event` and `Mu Event`. In this case, our internal component may use a provider syntax, but it may be in a vertical event, for example `Event (_ -> Element l p)`.

In this case, we can use a neat property of functions: they are _distributive_. In PureScript and Haskell, things following the distributive laws implement the [`Distributive`](https://pursuit.purescript.org/packages/purescript-distributive/6.0.0/docs/Data.Distributive#t:Distributive) typeclass. Amongst other things, it gives us a function:

```haskell
distribute :: forall a g. Functor g => g (f a) -> f (g a)
```

Here, our `g` is `Event` and our `f` is `Function a`. `distribute` will allow us to pull a provider to the outside of an event.

This is how the function `incrementToken` works. `incrementToken` creates an internal event bus, which is a common pattern in Deku to create internal logics for components.

```haskell
incrementToken
  :: forall l p. Event ({ token1 :: Event Number | _ } -> Domable l p)
incrementToken = bus \setN n -> do
  t1 <- asks _.token1
  let t1n = t1 <|> n
  pure $ plant $ D.div_
    [ D.p_ $ text_ "Increment token 1 locally, leaving it unchanged elsewhere."
    , D.button (map (attr D.OnClick <<< setN <<< add 1.0) t1n) (text_ "Increment")
    , D.p_ [ text_ "Token: ", text (map show t1n) ]
    ]
```

The provider is on the inside of the bus, so it looks and feels like all of our other components with the `do` notation, but the `bus` is in the way. Have no fear! We use `distribute` in `authorized` to make `incrementToken` behave like any other component in our provider-based setup.

```haskell
  incTok <- distribute incrementToken
```

## Interacting with providers in nested components

Providers can provide anything, but realistically, they'll often provide some sort of event to respond to as well as a means to modify that event. Providers can provide their own means of modification. For example, the function `keepRefreshOrInvalidate` creates a closure of type `Effect Unit` that we can call to get a new token and log us out if need be. By attaching it to the provider, we fish it out only where we need it, like we do in `cell3`:

```haskell
cell3 :: forall l p. { push1 :: Effect Unit | _ } -> Element l p
cell3 = do
  { push1 } <- ask
  pure
    ( D.td_ $ D.button (bang (D.OnClick := push1))
        (text_ "Do something needing token 1")
    )
```

In the example above, `cell3` is used deep down in the DOM to potentially log us out of our application if our token is expired.

## Metapolymorphism for the win

We haven't yet explored the underscore `_` in our application, but it is ubiquitous, so it's time to unpack it. To appreciate what it's doing, let's first take a tour down broken application lane by provoking a compile-time error.

Let's try to get a `foo` in `cell2`.

```haskell
cell2 :: forall l p. { foo :: Unit, token1 :: Event Number | _ } -> Element l p
cell2 = do
  t1 <- asks _.token1
  t2 <- asks _.foo
  pure (D.td_ $ text (("Token 1: " <> _) <<< show <$> t1))
```

The error message couldn't be clearer!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/po7upvnnnx55trh8uyce.png)

Everything else compiles fine (it's not `cell2`'s fault for asking for a `foo`), but at the toplevel, we are made aware that we failed to provide a `foo`.

What we've done is used the wildcard in PureScript, aka `_`, to ask the compiler to solve what the provider needs to be. I call this metapolymorphism instead of polymorphism because we are being _polymorphic in the meta-language_. What the heck does that mean? To do that, let's answer the following two questions:

- What is polymorphism?
- What is a programming language's meta-language?

Once these two questions are answered, we'll see that metapolymorphism is using polymorphism in the meta-language.

### What is polymorphism?

Polymorphism is a technique where, instead of providing a concrete type, we can provide a type variable that represents _any_ type. Here's an example:

```haskell
giveMeAnInt :: forall r. r -> Int
giveMeAnInt _ = 42

a = giveMeAnInt unit
b = giveMeAnInt 1.0
c = giveMeAnInt true
```

So far so good. Now, you may be thinking "Why didn't we just do this with our providers? For example, `authorized` can take anything, so why not use plain ol' polymorphism instead of getting all meta?" Let's try!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vd02hl2g3o9liidlsjbd.png)

Uh oh. The compiler sees that we have an `r`, which could be anything, but we don't need it to be anything! Specifically, we need it to have `push1`, `token1` and a bunch of other stuff. So it's polymorphic nature, while OK for consumers of `authorized`, does not help with the bits of DOM that find themselves within authorized. So we're in a bind. Or are we?

### What is a meta-language?

A [meta-language](https://en.wikipedia.org/wiki/Metalanguage) is the language used to talk about a language. The "language" of PureScript is comprised of things like types (`Element`), type variables (`l` or `p`), functions (`+`) and quantifiers (`forall`). They are the tools we can use to construct a valid program, and the type-checker is checking these things for correctness. Combined together, they create the objects and morphisms in the category **Purs**, which is comprised of the objects (types) and morphisms (functions) that create our program.

However, we don't just use the language when we're writing a PureScript program. In fact, we can't. We need the meta-language to tell the compiler certain things about how we're using the language. Some of these tidbits, like `.` after a `forall`, are meant to disambiguate our intention to the compiler. Others, like `import` statements, enrich the language by pulling new objects and morphisms into the scope of our program.

The type-level wildcard in PureScript, aka `_`, is part of the meta-language. It is not an object in **Purs**. Rather, it is an instruction to the compiler to figure out what the object will eventually be. When our program compiles, the compiler will solve the most general type that can occupy `_`. This type is part of the language.

For example, the following is solved as `Int`:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ypvsiy8efvokqo5szx8w.png)

The following is solved as a record with a field `x` that is an `Int` and other wise can contain anything:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jrwviqawg06u326ocdng.png)

And the following can be anything:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6u7y2lqcdodu7lvp9a2k.png)

Another way to think about it is that the `_` is polymorphic in the meta-langauge. It can morph into anything in the meta-language, as we just saw in the examples above, but in the language (the program as understood by the compiler) it is only ever one thing.

When working with providers, we use this technique to our advantage. We don't know what `_` is, nor do we care, at different internal levels of our provider scheme. It's just a placeholder for a type that will be filled in later. So long as we write a program that compiles, the compiler will fill it in. But crucially, if and when the program _doesn't_ compile, the compiler will give us helpful hints about what we're missing, as we saw when we tried to consume a value of `foo :: Unit` that was not provided.

I'm a big fan of this technique because it is super-lightweight, has crystal-clear error messages, and allows you to build up tidbits of a provider-based application with a relatively terse and straightforward syntax.

## Conclusion

I hesitated to use the word "metapolymorphic" when writing this article because it sounds as pretentious as it is, but that's what's going on here: by using polymorphism in the meta-language, we can create a an expressive, ergonomic and powerful provider system in a Deku app. Even if you're not using [Deku](https://github.com/mikesol/purescript-deku) yet (and as I wrote it a few weeks ago you're likely not...yet...) you can use this technique to your advantage to create provider-based architectures in your own applications. Happy providing!