heftia/README.md

298 lines
16 KiB
Markdown
Raw Normal View History

# Heftia: higher-order effects done right for Haskell
2023-09-18 10:07:42 +03:00
2023-09-27 10:44:34 +03:00
[![Hackage](https://img.shields.io/hackage/v/heftia.svg?logo=haskell&label=heftia)](https://hackage.haskell.org/package/heftia)
[![Hackage](https://img.shields.io/hackage/v/heftia-effects.svg?logo=haskell&label=heftia-effects)](https://hackage.haskell.org/package/heftia-effects)
2023-09-18 10:07:42 +03:00
2024-10-09 21:05:53 +03:00
Heftia is an extensible effects library that generalizes Algebraic Effects and Handlers to higher-order effects, providing users with maximum flexibility and delivering standard and reasonable speed.
In its generalization, the focus is on ensuring predictable results based on simple, consistent semantics, while preserving soundness.
2023-08-27 16:32:48 +03:00
2024-10-09 21:05:53 +03:00
Please refer to the [Haddock documentation](https://hackage.haskell.org/package/heftia-0.4.0.0/docs/Control-Monad-Hefty.html) for usage and semantics.
For information on performance, please refer to [performance.md](https://github.com/sayo-hs/heftia/blob/v0.4.0/benchmark/performance.md).
2024-10-09 21:05:53 +03:00
The library allows the following effects with well-defined semantics and composability:
* Coroutines
* Non-deterministic computations
* `MonadUnliftIO`
This library is inspired by the paper:
2023-08-27 16:32:48 +03:00
* Casper Bach Poulsen and Cas van der Rest. 2023. Hefty Algebras: Modular
Elaboration of Higher-Order Algebraic Effects. Proc. ACM Program. Lang. 7,
POPL, Article 62 (January 2023), 31 pages. <https://doi.org/10.1145/3571255>
2024-10-09 21:05:53 +03:00
The /elaboration/ approach proposed in the above paper allows for a straightforward treatment of higher-order effects.
2023-08-27 16:32:48 +03:00
2024-10-09 21:05:53 +03:00
Heftia's data structure is an extension of the Freer monad, designed to be theoretically straightforward by eliminating ad-hoc elements.
2023-08-25 07:23:49 +03:00
2024-07-06 16:45:56 +03:00
## Status
2024-07-17 10:29:56 +03:00
This library is currently in the beta stage.
There may be significant changes and potential bugs.
**We are looking forward to your feedback!**
2024-07-06 16:45:56 +03:00
2024-10-09 21:05:53 +03:00
## Getting Started
1.
```console
$ cabal update
```
2024-10-09 21:05:53 +03:00
2. Add `heftia-effects ^>= 0.4` and `ghc-typelits-knownnat ^>= 0.7` to the build dependencies. Enable the [ghc-typelits-knownnat](https://hackage.haskell.org/package/ghc-typelits-knownnat) plugin, `GHC2021`, and the following language extensions as needed:
* `LambdaCase`
* `DerivingStrategies`
* `DataKinds`
* `TypeFamilies`
* `BlockArguments`
* `FunctionalDependencies`
* `RecordWildCards`
* `DefaultSignatures`
* `PatternSynonyms`
Example .cabal:
```
...
build-depends:
...
2024-10-09 21:05:53 +03:00
heftia-effects ^>= 0.4,
ghc-typelits-knownnat ^>= 0.7,
default-language: GHC2021
default-extensions:
...
LambdaCase,
DerivingStrategies,
DataKinds,
TypeFamilies,
BlockArguments,
FunctionalDependencies,
RecordWildCards,
DefaultSignatures,
PatternSynonyms,
TemplateHaskell,
PartialTypeSignatures,
AllowAmbiguousTypes
ghc-options: ... -fplugin GHC.TypeLits.KnownNat.Solver
...
```
2024-10-09 21:05:53 +03:00
This library has been tested to work with GHC 9.8.2.
2024-07-09 19:26:05 +03:00
## Getting Started
2024-07-06 16:45:56 +03:00
## Example
Compared to existing Effect System libraries in Haskell that handle higher-order effects, this
library's approach allows for a more effortless and flexible handling of higher-order effects. Here
are some examples:
2024-07-09 19:26:05 +03:00
### Extracting Multi-shot Delimited Continuations
In handling higher-order effects, it's easy to work with **multi-shot delimited continuations**.
For more details, please refer to
2024-10-09 21:05:53 +03:00
the [example code](https://github.com/sayo-hs/heftia/blob/v0.4.0/heftia-effects/Example/Continuation/Main.hs).
2024-07-09 19:26:05 +03:00
### Two interpretations of the `censor` effect for Writer
Let's consider the following Writer effectful program:
```hs
hello :: (Tell String <: m, Monad m) => m ()
hello = do
tell "Hello"
tell " world!"
censorHello :: (Tell String <: m, WriterH String <<: m, Monad m) => m ()
censorHello =
censor
( \s ->
if s == "Hello" then
"Goodbye"
else if s == "Hello world!" then
"Hello world!!"
else
s
)
hello
```
For `censorHello`, should the final written string be `"Goodbye world!"` (Pre-applying behavior) ?
Or should it be `"Hello world!!"` (Post-applying behavior) ?
With Heftia, **you can freely choose either behavior depending on which higher-order effect interpreter (which we call an elaborator) you use**.
```hs
main :: IO ()
main = runEff do
(sPre, _) <-
runTell
2024-10-09 21:05:53 +03:00
. runWriterHPre @String
2024-07-09 19:26:05 +03:00
$ censorHello
(sPost, _) <-
runTell
2024-10-09 21:05:53 +03:00
. runWriterHPost @String
2024-07-09 19:26:05 +03:00
$ censorHello
liftIO $ putStrLn $ "Pre-applying: " <> sPre
liftIO $ putStrLn $ "Post-applying: " <> sPost
```
Using the `elabWriterPre` elaborator, you'll get "Goodbye world!", whereas with the `elabWriterPost` elaborator, you'll get "Hello world!!".
2024-07-09 19:26:05 +03:00
```
Pre-applying: Goodbye world!
Post-applying: Hello world!!
```
2024-10-09 21:05:53 +03:00
For more details, please refer to the [complete code](https://github.com/sayo-hs/heftia/blob/v0.4.0/heftia-effects/Example/Writer/Main.hs) and the [implementation of the elaborator](https://github.com/sayo-hs/heftia/blob/v0.4.0/heftia-effects/src/Control/Effect/Interpreter/Heftia/Writer.hs).
2024-10-09 21:05:53 +03:00
### Semantics Zoo
To run the [SemanticsZoo example](https://github.com/sayo-hs/heftia/blob/08f5cfe6a8f5c0383ea2b02e93326552400f7fd3/heftia-effects/Example/SemanticsZoo/Main.hs):
```console
$ git clone https://github.com/sayo-hs/heftia
$ cd heftia/heftia-effects
$ cabal run exe:SemanticsZoo
...
# State & Except
( evalState . runThrow . runCatch $ action ) = Right True
( runThrow . evalState . runCatch $ action ) = Right True
# NonDet & Except
( runNonDet . runThrow . runCatch . runChooseH $ action1 ) = [Right True,Right False]
( runThrow . runNonDet . runCatch . runChooseH $ action1 ) = Right [True,False]
( runNonDet . runThrow . runCatch . runChooseH $ action2 ) = [Right False,Right True]
( runThrow . runNonDet . runCatch . runChooseH $ action2 ) = Right [False,True]
2024-10-09 21:05:53 +03:00
# NonDet & Writer
( runNonDet . runTell . elaborateWriter . runChooseH $ action ) = [(3,(3,True)),(4,(4,False))]
( runTell . runNonDet . elaborateWriter . runChooseH $ action ) = (6,[(3,True),(4,False)])
# https://github.com/hasura/eff/issues/12
interpret SomeEff then runCatch : ( runThrow . runCatch . runSomeEff $ action ) = Right "caught"
runCatch then interpret SomeEff : ( runThrow . runSomeEff . runCatch $ action ) = Left "not caught"
2024-07-08 15:24:39 +03:00
2024-10-09 21:05:53 +03:00
[Note] All other permutations will cause type errors.
$
```
2023-09-18 05:38:40 +03:00
## Documentation
2024-10-09 21:05:53 +03:00
A detailed explanation of usage and semantics is available in [Haddock](https://hackage.haskell.org/package/heftia-0.4.0.0/docs/Control-Monad-Hefty.html).
The example codes are located in the [heftia-effects/Example/](https://github.com/sayo-hs/heftia/tree/v0.4.0/heftia-effects/Example) directory.
2024-07-13 15:46:13 +03:00
Also, the following *HeftWorld* example: https://github.com/sayo-hs/HeftWorld
2024-07-06 16:45:56 +03:00
2024-10-09 21:05:53 +03:00
About the internal /elaboration/ mechanism: https://sayo-hs.github.io/jekyll/update/2024/09/04/how-the-heftia-extensible-effects-library-works.html
2024-07-06 16:54:45 +03:00
## Comparison
2024-07-14 06:04:00 +03:00
* Higher-Order Effects: Does it support higher-order effects?
* Delimited Continuation: The ability to manipulate delimited continuations.
2024-08-25 15:22:42 +03:00
* Effect System: For a term representing an effectful program, is it possible to statically decidable a type that enumerates all the effects the program may produce?
2024-07-14 06:04:00 +03:00
* Purely Monadic: Is an effectful program represented as a transparent data structure that is a monad, and can it be interpreted into other data types using only pure operations without side effects or `unsafePerformIO`?
2024-07-15 13:32:28 +03:00
* Dynamic Effect Rewriting: Can an effectful program have its internal effects altered afterwards (by functions typically referred to as `handle with`, `intercept`, `interpose`, `transform`, `translate`, or `rewrite`) ?
2024-09-05 00:46:37 +03:00
For example, would it be possible to apply `interpose` as many times as the number of values input by the user at runtime?
* Semantics: Classification of behaviors resulting from the interpretation of effects.
2024-10-09 21:05:53 +03:00
* Algebraic Effects: The same as Algebraic Effects and Handlers.
* IO-fused: IO + ReaderT pattern.
* Carrier dependent: The behavior depends on the specific type inference result of the monad. Tagless-final style.
| Library or Language | Higher-Order Effects | Delimited Continuation | Effect System | Purely Monadic | Dynamic Effect Rewriting | Semantics |
| ------------------- | -------------------- | ---------------------- | --------------| --------------------------------- | ------------------------ | -------------------------------- |
| `heftia` | Yes | Multi-shot | Yes | Yes | Yes | Algebraic Effects |
| `freer-simple` | No | Multi-shot | Yes | Yes | Yes | Algebraic Effects |
| `polysemy` | Yes | No | Yes | Yes | Yes | Weaving-based (functorial state) |
| `effectful` | Yes | No | Yes | No (based on the `IO` monad) | Yes | IO-fused |
| `eff` | Yes | Multi-shot | Yes | No (based on the `IO` monad) | Yes | Algebraic Effects & IO-fused [^6]|
| `speff` | Yes | Multi-shot (restriction: [^4]) | Yes | No (based on the `IO` monad) | Yes | Algebraic Effects & IO-fused |
| `in-other-words` | Yes | Multi-shot? | Yes | Yes | No? | Carrier dependent |
| `mtl` | Yes | Multi-shot (`ContT`) | Yes | Yes | No | Carrier dependent |
| `fused-effects` | Yes | No? | Yes | Yes | No | Carrier dependent & Weaving-based (functorial state) |
| Koka-lang | No [^2] | Multi-shot | Yes | No (language built-in) | Yes | Algebraic Effects |
| OCaml-lang 5 | ? | One-shot | No [^3] | No (language built-in) | ? | Algebraic Effects? |
2024-08-25 15:22:42 +03:00
[^2]: https://gist.github.com/ymdryo/6fb2f7f4020c6fcda98ccc67c090dc75
2024-08-25 15:26:16 +03:00
[^3]: Effects do not appear in the type signature and can potentially cause unhandled errors at runtime
2024-09-13 18:23:36 +03:00
[^4]: Scoped Resumption only. e.g. Coroutines are not supported.
[^5]: https://github.com/sayo-hs/heftia/issues/12
2024-09-24 20:43:10 +03:00
[^6]: https://github.com/hasura/eff/issues/12
Heftia can simply be described as a higher-order version of freer-simple.
This is indeed true in terms of its internal mechanisms as well.
2023-09-16 15:32:23 +03:00
2024-10-09 21:05:53 +03:00
Additionally, this library provides a consistent algebraic effects semantics that is independent of carriers and effects.
On the other hand, in libraries like `in-other-words`, `mtl`, and `fused-effects`, the semantics of the code depend on the effect and, in part, the carrier inferred by type inference.
Fixing the semantics to a algebraic effects model helps improve the predictability of the behavior (interpretation result) of the code without losing flexibility.
2024-09-05 00:46:37 +03:00
2024-09-05 00:57:44 +03:00
Carrier-dependent semantics can lead to unexpected behavior for code readers, particularly in situations where the types become implicit.
2024-09-05 00:46:37 +03:00
Particularly, attention should be given to the fact that due to type inference, semantic changes may propagate beyond the blocks enclosed by `interpret` or `interpose`.
In the case of carrier-independent semantics, especially with Freer-based effects, `interpret` and `interpose` do not alter the semantics by intervening in type inference or instance resolution of the carrier.
Instead, they function as traditional functions, simply transforming the content of the data structure.
This results in minimal surprise to the mental model of the code reader.
2024-10-09 21:05:53 +03:00
### Performance
2024-07-15 13:32:28 +03:00
2024-10-09 21:05:53 +03:00
Overall, the performance of this library is positioned roughly in the middle between the fast and slow libraries, and can be considered average.
In all benchmarks, the speed is nearly equivalent to `freer-simple`, only slightly slower.
2024-07-15 13:32:28 +03:00
2024-10-09 21:05:53 +03:00
For more details, please refer to [performance.md](https://github.com/sayo-hs/heftia/blob/v0.4.0/benchmark/performance.md).
2024-07-15 13:32:28 +03:00
2024-10-09 21:05:53 +03:00
### Compatibility with other libraries
2024-07-15 13:32:28 +03:00
#### About mtl
* Since the representation of effectful programs in Heftia is simply a monad (`Eff`), it can be used as the base monad for transformers.
This means you can stack any transformer on top of it.
2024-10-03 00:33:23 +03:00
* The `Eff` monad is an instance of `MonadIO`, `MonadError`, `MonadRWS`, `MonadUnliftIO`, `Alternative`, etc., and these behave as the senders for the embedded `IO` or the effect GADTs defined in [data-effects](https://github.com/sayo-hs/data-effects).
2024-07-15 13:32:28 +03:00
2024-10-09 21:05:53 +03:00
#### Representation of effects
* Heftia relies on [data-effects](https://hackage.haskell.org/package/data-effects) for the definitions of standard effects such as `Reader`, `Writer`, and `State`.
* It is generally recommended to use effects defined with automatic derivation provided by [data-effects-th](https://hackage.haskell.org/package/data-effects-th).
* The representation of first-order effects is compatible with freer-simple.
Therefore, effects defined for freer-simple can be used as is in this library.
However, to avoid confusion between redundantly defined effects,
it is recommended to use the effects defined in `data-effects`.
* GADTs for higher-order effects are formally similar to Polysemy and fused-effects,
but they need to be instances of the [`HFunctor`](https://hackage.haskell.org/package/compdata-0.13.1/docs/Data-Comp-Multi-HFunctor.html#t:HFunctor) type class.
While it's not impossible to manually derive `HFunctor` for effect types based on these libraries and use them,
it's inconvenient, so it's better to use `data-effects`.
Also, it is not compatible with Effectful and eff.
## Future Plans
2024-10-09 21:05:53 +03:00
* Support for Applicative effects.
* Completing lacking definitions such as
* interpreters for the `Accum` and others effects
and others.
## License
2024-10-09 21:05:53 +03:00
The license is MPL 2.0. Please refer to the [NOTICE](https://github.com/sayo-hs/heftia/blob/v0.4.0/NOTICE).
2024-08-31 23:19:45 +03:00
Additionally, this README.md and the documents under the `docs-ja` directory are licensed
under CC BY-SA 4.0.
2023-08-25 07:23:49 +03:00
## Your contributions are welcome!
2024-10-09 21:05:53 +03:00
Please see [CONTRIBUTING.md](https://github.com/sayo-hs/heftia/blob/v0.4.0/CONTRIBUTING.md).
2023-09-18 09:32:37 +03:00
2024-09-08 19:38:26 +03:00
## Acknowledgements, citations, and related work
The following is a non-exhaustive list of people and works that have had a significant impact, directly or indirectly, on Heftias design and implementation:
- Oleg Kiselyov, Amr Sabry, and Cameron Swords — [Extensible Effects: An alternative to monad transfomers][oleg:exteff]
- Oleg Kiselyov and Hiromi Ishii — [Freer Monads, More Extensible Effects][oleg:more]
- Rob Rix, Patrick Thomson, and other contributors — [`fused-effects`][gh:fused-effects]
- Sandy Maguire and other contributors — [`polysemy`][gh:polysemy]
- Alexis King and other contributors — [`freer-simple`][gh:freer-simple], [`eff`][gh:eff]
- Casper Bach Poulsen and Cas van der Rest — [Hefty Algebras: Modular Elaboration of Higher-Order Algebraic Effects][casper:hefty]
[gh:fused-effects]: https://github.com/fused-effects/fused-effects
[gh:polysemy]: https://github.com/polysemy-research/polysemy
[oleg:exteff]: http://okmij.org/ftp/Haskell/extensible/exteff.pdf
[oleg:more]: http://okmij.org/ftp/Haskell/extensible/more.pdf
[casper:hefty]: https://dl.acm.org/doi/10.1145/3571255
[gh:freer-simple]: https://github.com/lexi-lambda/freer-simple
[gh:eff]: https://github.com/lexi-lambda/eff