Skip to content

Commit

Permalink
Merge pull request #2 from fpco/readme-improvements
Browse files Browse the repository at this point in the history
readme improvements
  • Loading branch information
snoyberg committed Jul 14, 2017
2 parents 6c6ff5a + bb72cb9 commit f8a4cdd
Showing 1 changed file with 34 additions and 8 deletions.
42 changes: 34 additions & 8 deletions unliftio/README.md
Expand Up @@ -129,8 +129,20 @@ how to use `MonadUnliftIO` in practice. And for many cases, you can
simply add the `MonadUnliftIO` constraint and then use the
pre-unlifted versions of functions (like
`UnliftIO.Exception.catch`). But ultimately, you'll probably want to
use the typeclass directly. Here are some simple examples. First: some
typeclass instances:
use the typeclass directly. The type class has only one method --
`askUnliftIO`:

```haskell
newtype UnliftIO m = UnliftIO { unliftIO :: forall a. m a -> IO a }

class MonadIO m => MonadUnliftIO m where
askUnliftIO :: m (UnliftIO m)
```

`askUnliftIO` gives us a function to run arbitrary computation in `m`
in `IO`. Thus the "unlift": it's like `liftIO`, but the other way around.

Here are some sample typeclass instances:

```haskell
instance MonadUnliftIO IO where
Expand All @@ -155,7 +167,16 @@ Note that:
* `ReaderT` is just like `IdentityT`, but it captures the reader
environment when starting.

Second, using `withRunInIO` to unlift a function:
We can use `askUnliftIO` to unlift a function:

```haskell
timeout :: MonadUnliftIO m => Int -> m a -> m (Maybe a)
timeout x y = do
u <- askUnliftIO
System.Timeout.timeout x $ unliftIO u y
```

or more concisely using `withRunIO`:

```haskell
timeout :: MonadUnliftIO m => Int -> m a -> m (Maybe a)
Expand All @@ -164,9 +185,11 @@ timeout x y = withRunInIO $ \run -> System.Timeout.timeout x $ run y

This is a common pattern: use `withRunInIO` to capture a run function,
and then call the original function with the user-supplied arguments,
applying `run` as necessary.
applying `run` as necessary. `withRunIO` takes care of invoking
`unliftIO` for us.

Thirdly, using `askUnliftIO` directly when multiple types are needed:
However, if we want to use the run function with different types, we
must use `askUnliftIO`:

```haskell
race :: MonadUnliftIO m => m a -> m b -> m (Either a b)
Expand All @@ -175,7 +198,7 @@ race a b = do
liftIO (A.race (unliftIO u a) (unliftIO u b))
```

or more idiomatically using `withUnliftIO`:
or more idiomatically `withUnliftIO`:

```haskell
race :: MonadUnliftIO m => m a -> m b -> m (Either a b)
Expand All @@ -187,7 +210,7 @@ of `run`, which is polymorphic. You _could_ get away with multiple
`withRunInIO` calls here instead, but this approach is idiomatic and
may be more performant (depending on optimizations).

And finally, a much more complex usage, when unlifting the `mask`
And finally, a more complex usage, when unlifting the `mask`
function. This function needs to unlift vaues to be passed into the
`restore` function, and then `liftIO` the result of the `restore`
function.
Expand Down Expand Up @@ -293,7 +316,10 @@ reasons:

* `MonadUnliftIO` is a simple typeclass, easy to explain. We don't
want to complicated matters (`MonadBaseControl` is a notoriously
difficult to understand typeclass)
difficult to understand typeclass). This simplicity
is captured by the laws for `MonadUnliftIO`, which make the
behavior of the run functions close to that of the already familiar
`lift` and `liftIO`.
* Having this kind of split would be confusing in user code, when
suddenly `finally` is not available to us. We would rather encourage
[good practices](https://www.fpcomplete.com/blog/2017/06/readert-design-pattern)
Expand Down

0 comments on commit f8a4cdd

Please sign in to comment.