Piloting Puppeteer with PureScript - Part 3
Tags: webdev, testing, functional
Published: 2021-02-05 16:56:25

---
title: Piloting Puppeteer with PureScript - Part 3
published: true
description: 
tags:
  - webdev
  - testing
  - functional
cover_image: https://images.unsplash.com/photo-1508751252012-9ed1d2377ca5?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80
---

In the previous article in this series, we saw how to use the PureScript FFI to make PureScript code interoperable with JS.

In this article, I'd like to talk more about the `Aff` monad and how we use the type system to make sure all roads lead to `Aff`.

# The Aff monad, fibers, and asynchronous code

Asynchronous code is the bane of many-a-JS-programmer's existence. Race conditions, timeouts, forgetting to return `await promise` and instead returning `promise`, and other classic errors mar asynchronous JS code. Some libraries, like `redux-saga`, go a long way towards normalizing asynchronous practices, but they require significantly refactoring code so that all communication runs through a state machine.

Fibers fix that. A `Fiber` is an asynchronous computation that can be spawned, forked, suspended, joined, killed, cancelled, delayed attempted, and run in parallel. [Aff](https://pursuit.purescript.org/packages/purescript-aff/5.1.2/docs/Effect.Aff) supports all of these actions, making it a one-stop shop for asynchronous computation.  As a result, all asynchronous computation flows through the `Aff` monad in PureScript, which can be used in any effectful context by causing `launchAff_`.  Here's an example

```haskell
main :: Effect Unit
main = do
  launchAff_ $ log ("hello world")
  launchAff_ do
    delay (Milliseconds 400)
    log ("goodbye world")
```

`Aff` gives a programmer all the tools they need to orchestrate complex asynchronous workflows in addition to eliminating various common bugs in JS asynchronous code.

# The road to Aff

As mentioned before, JavaScript has three main conventions for interacting with code asynchronously:

1. Callbacks
1. Promises
1. `async/await`

The PureScript ecosystem has several libraries that make these conversions pretty painless.  For example, let's say you have a JS FFI function (aka an `Effect`) that returns a promise:

```javascript
exports.myPromise_ = function() { return Promise.resolve(true); }
```

In PureScript, you can use `toAffE` from `purescript-aff-promise` to convert this to an Aff.

```haskell
foreign import myPromise_ :: Effect (Promise Boolean)
myPromise = toAffE myPromise_ :: Aff Boolean
```

While this suffices for simple code, it gets clunky when working with complex type signatures. For example, a common case is working with multi-argument functions.

```haskell
foreign import myPromiseF_ :: Int -> Int -> Effect (Promise Boolean)
myPromiseF a b = toAffE (myPromiseF_ a b) :: Int -> Int -> Aff Boolean
```

The issue here is that there is duplication in two places:

1. We don't want to pass `myPromiseF_` arguments if we know the order will be exactly the same as to `myPromiseF`.
1. We don't want to rewrite the type of we know that all that changes is `Aff`.

Both of these issues are solved with type classes - a powerful feature of both Haskell and PureScript that leads to more concise, readable, "boring" and predictable code with less chance for bugs (ie swapping arguments or accidentally specializing a polymorphic function too early).

# Type classes to the rescue!

A typeclass is collection of groups of types for which a set of functions produce a result based on properties of the input type group.  In this article, we'd like to identify a collection of types that can be "lifted" into `Aff`-land with a function we'll called `asAff`. In the table below, `a`, `b` and `c` are wildcards for any type.

------------------
| type  | as Aff |
| ----  | ------ |
| `Aff a`  | `Aff a`  |
| `Promise a` | `Aff a`  |
| `Effect (Promise a)` | `Aff a`  |
| `Effect a` | `Aff a` |
| `a -> Promise b` | `a -> Aff b` |
| `a -> b -> Promise c` | `a -> b -> Aff c` |
| `a -> b -> Effect (Promise c)` | `a -> b -> Aff c` |
---------------

So, for example, we'd like `asAff` applied to a function with type `Int -> String -> Promise Boolean` to return a function with type `Int -> String -> Aff Boolean`, etc.

First, let's define a typeclass `Affable` with a method `asAff`.

```haskell
class Affable a b | a -> b where
  asAff :: a -> b
```

You can think of typeclasses like the table above. They relate types. In this case, our table has two types, column `a` and column `b`. Column `a` has a `many-to-one` relationship to column `b` - in other words, `b` is a _function_ of `a`. So we can write `a -> b` after the class definition and we call this a _functional dependency_. In addition to being a useful annotation, it helps the compiler resolve instances of typeclasses, as we'll see below.

Now, let's walk through the four three instances from our table.

```haskell
instance affAffable :: Affable (Aff a) (Aff a) where
  asAff = identity

instance promiseAffable :: Affable (Promise a) (Aff a) where
  asAff = toAff

instance effectPromiseAffable :: Affable (Effect (Promise a)) (Aff a) where
  asAff = toAffE

instance effectAffable :: Affable (Effect a) (Aff a) where
  asAff = liftEffect
```

One important thing to notice is the type variable `a`. This is what makes the definition of `asAff` consistent. If we just had `Affable Effect Aff`, it would only be allowable if the original class definition of `asAff` treated `a` as a type of kind `Type -> Type`. In fact, we could have done that!  Let's look at an alternate world where we would have defined `Affable` using type constructors.

```haskell
class Affable' a b | a -> b where
  asAff' :: forall x. a x -> b x
```

This is also called a _natural transformation_ between `a` and `b`, and we could have written it using PureScript's natural transformation operator `~>` like so:

```haskell
class Affable' a b | a -> b where
  asAff' :: a ~> b
```

Had we done this, the instances would have looked like:

```haskell
instance affAffable' :: Affable' Aff Aff where
  asAff = identity
-- etc.
```

So if we _could_ have done that, you're probably wondering why _didn't_ we. Good question! Natural transformations only work when the transformation is universal, meaning it could work for _any_ type. If it only works for some types, we are out of luck.

To build up the bottom part of the table (the one with functions), we no longer can use a type constructor of type `Type -> Type`.  Let's take, for example, `Function a (Promise b)` and `Function a (Aff b)`. What would a natural transformation be like between those two? Type constructors like `Function a` and `Promise` resemble functions a great deal - they act on types as functions act on values - so we could think of it as "composing" `Function a` (which is of type `Type -> Type`, as `Function` is of type `Type -> Type -> Type`) and `Promise` a la `Function a <<< Promise`. This in fact _is_ a natural transformation, as typelevel-composition exists in theory, but the PureScript compiler (and most compilers) can't figure this out (yet).  This is because the relationships between types are expressed exclusively through typeclasses, so if two types can be composed, they would have to either implement a typeclass called `Compose` or be part of a `Compose` data structure that was constrained by other typeclasses. [`purescript-typelevel-eval`](https://pursuit.purescript.org/packages/purescript-typelevel-eval) does exactly this.

The main reason we do not use natural transformations here, however, is because they fail to hold when working with functions in this context.  Natural transformations can be thought of containers of types that are ignorant of the carrier type. For example, a natural transformation of type `List ~> Maybe` does not care if it is a list of integers, strings, or cars. However, what we actually want is not to enumerate all possible type constructors in the covariant position of the function (ie `Function a <<< Promise`, `Function a <<< Effect`, etc) but rather to make the more general statement that we are going from `Function a b` to `Function a c` here, where `b` and `c` have a specific constraint: they must have an `Affable` relationship. Thus, we've peeked inside the function to the covariant types `b` and `c`, destroying naturality but gaining a really elegant definition, as we'll see below.

As a motivating example, let's start with `Function x (Aff y)` as our `a` and `Function x (Aff y)` as our `b`. As with `Aff x` and `Aff y`, we'd imagine some sort of identity transformation that simply passes through the `Aff y`.  In the case of `Function x (Promise y)` as our `a`, we want the `toAff` transformation. So let's constrain the element in the covariant (rightmost) position of the function to behave as it would in `Affable`.

```haskell
instance fnAffable :: Affable b c => Affable (Function a b) (Function a c) where
  asAff = (<<<) asAff
```

Here, we use composition to postpend the `asAff` operation.  It's the same exactly thing as writing `asAff a x = asAff (a x)`.

The nice thing about this is, for free, we've gotten all our functions on the table above.  Meaning functions of 2, 3, 4, 42, 101, and _n_ arguments all work the same. To see why, remember that we've now defined `Function a b` as `Affable` if `b` is `Affable`. So what could `b` be? Any `Affable`, including `Function c d` if `d` is `Affable`. But what could `d` be? Why `Function x y` if `y` is `Affable`. But what could `y` be? You get the idea. We have `Function a (Function c (Function x y))` or, to use the more common infix operator, `a -> c -> x -> y` where `y` is `Affable`.

# Affable functions

Now, just by using a single function, we can take something of the signature ie `Browser -> Page -> Promise a` and get a `Browser -> Page -> Aff a` automatically.

```haskell
foreign import freeResources_ :: Browser -> Page -> Promise Unit
freeResources = asAff freeResources_ :: Browser -> Page -> AffUnit
```

What about those function signatures? Is there some way we can prevent copying them? You bet! `Affable` works on types exactly like `asAff` works on functions. So we can create a type synonym, call it `Affize`, that works like so:

```haskell
type Affize a
  = forall b. Affable a b => b
```

And now we can do:

```haskell
type FreeResources_ = Browser -> Page -> Promise Unit
foreign import freeResources_ :: FreeResources_
freeResources = asAff freeResources_ :: Affize FreeResources_ 
```

And voila! Concurrent code in the `Aff` monad for free using recursion on the function _and_ type level thanks to typeclasses.

# Conclusion

Asynchronous code is inherently fragile and prone to errors. Having a consistent pattern for managing asynchronicity (`Aff`) as well an easy way to marshal functions into the asynchronous context (`Affable`) along with helpers to create type annotations (`Affize`) help reduce a host of bugs in the domain of race conditions, deadlocks and error handling.  I hope you'll get the chance to try [PureScript](https://purescript.org), `Aff`, and [Meeshkan](https://meeshkan.com) (where all this ingests millions of daily events!). Enjoy!