Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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
- Host: GitHub
- URL: https://github.com/fumieval/oath
- Owner: fumieval
- License: bsd-3-clause
- Created: 2021-12-03T00:52:11.000Z (about 3 years ago)
- Default Branch: master
- Last Pushed: 2023-01-03T14:45:04.000Z (about 2 years ago)
- Last Synced: 2024-11-01T01:33:19.743Z (2 months ago)
- Language: Haskell
- Homepage:
- Size: 15.6 KB
- Stars: 30
- Watchers: 4
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
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 tidevalOath :: 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
```