Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/fumieval/oath

Composable Concurrent Computation Done Right
https://github.com/fumieval/oath

Last synced: 2 months ago
JSON representation

Composable Concurrent Computation Done Right

Awesome Lists containing this project

README

        

Oath: composable concurrent computation done right
----

Oath is an Applicative structures that makes concurrent actions composable.

```haskell
newtype Oath a = Oath { runOath :: forall r. (STM a -> IO r) -> IO r }
```

`Oath` is a continuation-passing IO action which takes a transaction to obtain the final result (`STM a`).
The continuation-passing style makes it easier to release resources in time.
The easiest way to construct `Oath` is `oath`. It runs the supplied IO action in a separate thread as long as the continuation is running.

```haskell
oath :: IO a -> Oath a
oath act = Oath $ \cont -> do
v <- newEmptyTMVarIO
tid <- forkFinally act (atomically . putTMVar v)
let await = takeTMVar v >>= either throwSTM pure
cont await `finally` killThread tid

evalOath :: Oath a -> IO a
evalOath m = runOath m atomically
```

`Oath` is an `Applicative`, so you can combine multiple `Oath`s using `<*>`. `Oath` combined this way kicks off computations without waiting for the results. The following code runs `foo :: IO a` and `bar :: IO b` concurrently, then applies `f` to these results.

```haskell
main = evalOath $ f <$> oath foo <*> oath bar
```

It _does not_ provide a Monad instance because it is logically impossible to define one consistent with the Applicative instance.

Usage
----

`Oath` abstracts a triple of sending a request, waiting for response, and cancelling a request. If you want to send requests in a deterministic order, you can construct `Oath` directly instead of calling `oath`.

```haskell
Oath $ \cont -> bracket sendRequest cancelRequest (cont . waitForResponse)
```

Timeout behaviour can be easily added using the `Alternative` instance and `delay :: Int -> Oath ()`. `a <|> b` runs both computations until one of them returns a result, then cancels the other.

```haskell
-- | An 'Oath' that finishes once the given number of microseconds elapses
delay :: Int -> Oath ()

oath action <|> delay 100000
```

Comparison to other packages
----

[future](https://hackage.haskell.org/package/future-2.0.0/docs/Control-Concurrent-Future.html), [caf](https://hackage.haskell.org/package/caf-0.0.3/docs/Control-Concurrent-Futures.html) and [async](https://hackage.haskell.org/package/async-2.2.4/docs/Control-Concurrent-Async.html) seem solve the same problem. They define abstractions to asynchronous computations. `async` has an applicative `Concurrently` wrapper.

[spawn](https://hackage.haskell.org/package/spawn-0.3/docs/Control-Concurrent-Spawn.html) does not define any datatype. Instead it provides an utility function for `IO` (`spawn :: IO a -> IO (IO a)`). It does not offer a way to cancel a computation.

[promises](https://hackage.haskell.org/package/promises-0.3/docs/Data-Promise.html) provides a monadic interface for pure demand-driven computation. It has nothing to do with concurrency.

[unsafe-promises](https://hackage.haskell.org/package/unsafe-promises-0.0.1.3/docs/Control-Concurrent-Promise-Unsafe.html) creates an IO action that waits for the result on-demand using `unsafeInterleaveIO`.

[futures](https://hackage.haskell.org/package/futures-0.1/docs/Futures.html) provides a wrapper of `forkIO`. There is no way to terminate an action and it does not propagate exceptions.

[promise](https://hackage.haskell.org/package/promise-0.1.0.0/docs/Control-Concurrent-Promise.html) has illegal Applicative and Monad instances; `(<*>)` is not associative and its `ap` is not consistent with `(<*>)`.

Performance
----

```haskell
bench "oath 10" $ nfIO $ O.evalOath $ traverse (O.oath . pure) [0 :: Int ..9]
bench "async 10" $ nfIO $ A.runConcurrently $ traverse (A.Concurrently . pure) [0 :: Int ..9]
```

`Oath`'s overhead of `(<*>)` is less than `Concurrently`. Unlike `Concurrently`, `<*>` itself does not fork threads.

```
All
oath 10: OK (1.63s)
5.78 μs ± 265 ns
async 10: OK (0.21s)
12.3 μs ± 767 ns
oath 100: OK (0.22s)
52.6 μs ± 4.4 μs
async 100: OK (0.23s)
109 μs ± 8.4 μs
```