https://github.com/inflex-io/early
Add early return to any do-expression
https://github.com/inflex-io/early
haskell
Last synced: 3 months ago
JSON representation
Add early return to any do-expression
- Host: GitHub
- URL: https://github.com/inflex-io/early
- Owner: inflex-io
- License: other
- Created: 2021-01-01T18:44:29.000Z (about 5 years ago)
- Default Branch: main
- Last Pushed: 2021-07-02T11:02:17.000Z (over 4 years ago)
- Last Synced: 2025-10-21T13:55:48.677Z (3 months ago)
- Topics: haskell
- Language: Haskell
- Homepage:
- Size: 54.7 KB
- Stars: 47
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# early
Add early return to any `do`-expression
**Table of Contents**
- [early](#early)
- [Description](#description)
- [How it works](#how-it-works)
- [Details](#details)
- [Why not `ExceptT` or exceptions?](#why-not-exceptt-or-exceptions)
- [Inspiration](#inspiration)
- [Special thanks](#special-thanks)
## Description
This package is a GHC plugin to add special syntax for early return in
`do`-notation. It provides a way to terminate the current
`do`-expression with a result, usually a failure result, but not
necessarily. It should not be confused with an exception handler. It
uses regular values everywhere.
## How it works
The plugin is enabled in any module via a pragma.
``` haskell
{-# OPTIONS -F -pgmF=early #-}
```
The syntax `?` can be added to the end of any `do` statement to make
it short-circuit when the action produces a certain "stop" result
(such as `Left`, or `Nothing`; **the particular type is type-class
based**, see the Details section below).
Suppose that `grabEnv :: String -> IO (Either Error String)`, then you
can write this:
```haskell
app :: IO (Either Error String)
app = do
path <- grabEnv "PATH"?
putStrLn "Look ma, no lifts!"
magic <- grabEnv "MAGIC"?
pure (Right (path ++ magic))
```
Note the final `pure` in the do should wrap the type, as the type of
the whole `do`-block has changed.
That's it! See `test/Main.hs` for full example.
## Details
The syntax `stmt?` is desugared in this way:
* `do stmt?; next` becomes `do earlyThen stmt next`
* `do pat <- stmt?; next; next2` becomes `do early stmt (\pat -> do next; next2; ...)`
The `early` and `earlyThen` are driven by the `Early` class, which any
functor-like data type can implement.
``` haskell
early :: (Monad m, Early f) => m (f a) -> (a -> m (f b)) -> m (f b)
earlyThen :: (Monad m, Early f) => m (f a) -> m (f b) -> m (f b)
```
``` haskell
class Functor f => Early f where
dispatch :: Applicative m => f a -> (a -> m (f b)) -> m (f b)
```
Two provided instances out of the box are `Either e` and `Maybe`, but
others can be added freely, such as a `Failure e a` type of your
library, etc.
### Why not `ExceptT` or exceptions?
Full explanation here:
[my recoverable errors post](https://chrisdone.com/posts/recoverable-errors-in-haskell/).
Because `ExceptT` (or `ContT`) cannot be an
instance of `MonadUnliftIO`. It is not unliftable; this means that
exceptions, cleanup and concurrency don't have an interpretation. This
is an area where monad transformers in `mtl`/`transformers` don't
compose. Other free monads commute, but then you have to use a free
monad which has a complicated story regarding performance.
## Inspiration
The syntax and concept of using simple return values for early
termination and failure handling is inspired
[by Rust's error handling](https://doc.rust-lang.org/rust-by-example/error/result/enter_question_mark.html). The
`Early` class resembles the
[Try trait](https://doc.rust-lang.org/std/ops/trait.Try.html), but is
slightly different, as Haskell has higher-kinded types.
Additionally, one can take a Rust-like view of error handling in
Haskell:
|Use-case|Haskell|Rust|
|---:|---:|---:|
|Unrecoverable errors|Throwing exceptions|Panics|
|Recoverable errors|Return `Either`/`Maybe`|Return `Result`/`Some`|
This plugin allows one to structure their code in such a way.
## Future Work
A small library of short-circuiting `traverse`/`fold` would let one
use actions that return `Either`/`Maybe`.
## Special thanks
The following people's work helped me a lot to get my work done faster:
* Shayne Fletcher and Neil Mitchell https://github.com/digital-asset/ghc-lib
* Oleg Grenrus https://github.com/phadej/idioms-plugins
* Mark Karpov https://github.com/mrkkrp/ghc-syntax-highlighter