heftia/README.md

273 lines
14 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-01-07 07:20:55 +03:00
Heftia is a higher-order effects version of Freer.
2023-08-27 16:32:48 +03:00
This library provides "[continuation-based semantics](https://github.com/lexi-lambda/eff/blob/master/notes/semantics-zoo.md)" for higher-order effects, the same as [lexi-lambda's eff](https://github.com/lexi-lambda/eff).
Instead of using the `IO` monad to implement delimited continuations for effects, Heftia internally uses `Freer` monad.
2023-08-27 16:32:48 +03:00
The paper
* 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>
inspires this library.
Hefty trees, proposed by the above paper, are extensions of free monads,
allowing for a straightforward treatment of higher-order effects.
This library offers Hefty monads and Freer monads, encoded into data
2023-08-27 16:32:48 +03:00
types in several ways to enable tuning in pursuit of high performance.
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
## Installation
1.
```console
$ cabal update
```
2. Add `heftia-effects ^>= 0.2` 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`
* `TemplateHaskell`
* `PartialTypeSignatures`
* `AllowAmbiguousTypes`
Example .cabal:
```
...
build-depends:
...
heftia-effects ^>= 0.2,
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
...
```
This library has been tested to work with GHC 9.2.8.
2024-07-09 19:26:05 +03:00
## Getting Started
To run the [SemanticsZoo example](https://github.com/sayo-hs/heftia/blob/232599fbdfaf13dcf009f40585369e0ec71fd0bd/heftia-effects/Example/SemanticsZoo/Main.hs):
2024-07-09 19:26:05 +03:00
```console
$ git clone https://github.com/sayo-hs/heftia
$ cd heftia/heftia-effects
$ cabal run exe:SemanticsZoo
2024-07-09 19:26:05 +03:00
...
# 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]
# NonDet + Writer
( runNonDet . runTell . elaborateWriter . runChooseH $ action ) = [(3,(3,True)),(4,(4,False))]
( runTell . runNonDet . elaborateWriter . runChooseH $ action ) = (6,[(3,True),(4,False)])
[Note] All other permutations will cause type errors.
2024-07-09 19:26:05 +03:00
$
```
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**.
This enables an almost complete emulation of "Algebraic Effects and Handlers".
For more details, please refer to
the [example code](https://github.com/sayo-hs/heftia/blob/232599fbdfaf13dcf009f40585369e0ec71fd0bd/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
. interpretRecH (elabWriterPre @String)
2024-07-09 19:26:05 +03:00
$ censorHello
(sPost, _) <-
runTell
. interpretRecH (elabWriterPost @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!!
```
For more details, please refer to the [complete code](https://github.com/sayo-hs/heftia/blob/develop/heftia-effects/Example/Writer/Main.hs) and the [implementation of the elaborator](https://github.com/sayo-hs/heftia/blob/develop/heftia-effects/src/Control/Effect/Handler/Heftia/Writer.hs).
Furthermore, the structure of Heftia is theoretically straightforward, with ad-hoc elements being
eliminated.
2024-07-08 15:24:39 +03:00
Additionally, Heftia supports not only monadic effectful programs but also **applicative effectful programs**.
This may be useful when writing concurrent effectful code.
2023-12-28 04:18:12 +03:00
Heftia is the current main focus of the [Sayo Project](https://github.com/sayo-hs).
2023-09-18 05:38:40 +03:00
## Documentation
The example codes are located in the [heftia-effects/Example/](https://github.com/sayo-hs/heftia/tree/232599fbdfaf13dcf009f40585369e0ec71fd0bd/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
~~Examples with explanations can be found in the [docs/examples/](https://github.com/sayo-hs/heftia/tree/master/docs/examples) directory.~~ Documents have become outdated.
2024-07-06 16:54:45 +03:00
Please wait for the documentation for the new version to be written.
2024-07-14 05:30:51 +03:00
## Limitation and how to avoid it
### The *reset* behavior of the scopes held by unhandled higher-order effects
When attempting to interpret an effect while there are unhandled higher-order effects present, you cannot obtain delimited continuations beyond the action scope held by these unhandled higher-order effects.
It appears as if a *reset* (in the sense of *shift/reset*) is applied to each of the scopes still held by the remaining unhandled higher-order effects.
2024-07-15 13:32:28 +03:00
In other words, to obtain delimited continuations beyond their scope, it is necessary to first handle and eliminate all higher-order effects that hold those scopes,
2024-07-14 05:30:51 +03:00
and then handle the effect targeted for stateful interpretation in that order.
2024-07-15 13:32:28 +03:00
For this purpose, it might sometimes be possible to use *multi-layering*. For an example of multi-layering,
see `handleReaderThenShift` defined in [Example/Continuation2](https://github.com/sayo-hs/heftia/blob/8f71a2d4e6125018b64cbbacd32151565a29046d/heftia-effects/Example/Continuation2/Main.hs)
(particularly, the type signature of `prog` within it).
2024-07-14 05:30:51 +03:00
For more details, please refer to the documentation of the `interpretRec` family of functions.
## 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.
* Statically Typed Set of Effects: For a term representing an effectful program, is it possible to statically decidable a type that enumerates all the effects the program may produce?
* 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-07-14 06:04:00 +03:00
* Performance: Time complexity or space complexity.
| Library or Language | Higher-Order Effects | Delimited Continuation | Statically Typed Set of Effects | Purely Monadic | Dynamic Effect Rewriting | Performance (TODO) |
| ------------------- | -------------------- | ---------------------- | ----------------------------------------------- | --------------------------------- | ------------------------ | ------------------ |
2024-07-14 05:30:51 +03:00
| Heftia | Yes [^1] | Multi-shot | Yes | Yes (also Applicative and others) | Yes | ? |
| freer-simple | No | Multi-shot | Yes | Yes | Yes | ? |
| Polysemy | Yes | No | Yes | Yes | Yes | ? |
| Effectful | Yes | No | Yes | No (based on the `IO` monad) | Yes | ? |
2024-07-14 06:04:00 +03:00
| eff | Yes | Multi-shot? | Yes | No (based on the `IO` monad) | Yes | Fast |
| mtl | Yes | Multi-shot (`ContT`) | Yes | Yes | No | ? |
| fused-effects | Yes | No? | Yes | Yes | No | ? |
| koka-lang | No? | Multi-shot | Yes | No (language built-in) | ? | ? |
| OCaml-lang 5 | Yes | One-shot | No [^2] | No (language built-in) | ? | ? |
2024-07-14 05:30:51 +03:00
[^1]: limitation: https://github.com/sayo-hs/heftia?tab=readme-ov-file#the-reset-behavior-of-the-scopes-held-by-unhandled-higher-order-effects
[^2]: potential for 'unhandled' runtime errors
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-07-15 13:32:28 +03:00
### Compatibility with other libraries
#### Representation of effects
* Heftia Effects relies on [data-effects](https://github.com/sayo-hs/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://github.com/sayo-hs/data-effects/tree/develop/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](https://github.com/sayo-hs/data-effects).
* GADTs for higher-order effects 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 for convenient usage.
While it is still possible to use them without being instances of `HFunctor`,
the `interpretRec` family of functions cannot be used when higher-order effects that are not `HFunctor` are unhandled.
If this issue is not a concern, the GADT representation of higher-order effects is compatible with Polysemy and fused-effects.
It is not compatible with Effectful and eff.
#### 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.
* The `Eff` monad is an instance of `MonadIO`, `MonadError`, `MonadRWS`, 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).
## Future Plans
2024-07-17 10:29:56 +03:00
* Enriching the documentation and tests
* Completing missing definitions such as
* more patterns of interpret & transform function-families.
* handlers for the `Accum` and others effect classes
and others.
* Benchmarking
## License
The license is MPL 2.0. Please refer to the [NOTICE](https://github.com/sayo-hs/heftia/blob/232599fbdfaf13dcf009f40585369e0ec71fd0bd/NOTICE).
Additionally, this README.md and the documents under the `docs`/`docs-ja` directory are licensed
under CC BY-SA 4.0.
2023-08-25 07:23:49 +03:00
## Your contributions are welcome!
Please see [CONTRIBUTING.md](https://github.com/sayo-hs/heftia/blob/232599fbdfaf13dcf009f40585369e0ec71fd0bd/CONTRIBUTING.md).
2023-09-18 09:32:37 +03:00
## Credits
Parts of this project have been inspired by the following resources:
2023-09-18 09:32:37 +03:00
* **[Hefty Algebras -- The Artifact](https://github.com/heft-lang/POPL2023)**
* **Copyright** (c) 2023 Casper Bach Poulsen and Cas van der Rest
* **License**: MIT