MyNixOS website logo
Description

Early return syntax in do-notation (GHC plugin)

Please see the README on GitHub at https://github.com/inflex-io/early#readme

early

Add early return to any do-expression

Table of Contents

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.

{-# 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:

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.

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)
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.

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. The Early class resembles the Try trait, but is slightly different, as Haskell has higher-kinded types.

Additionally, one can take a Rust-like view of error handling in Haskell:

Use-caseHaskellRust
Unrecoverable errorsThrowing exceptionsPanics
Recoverable errorsReturn Either/MaybeReturn 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.
Metadata

Version

0.0.0

Platforms (75)

    Darwin
    FreeBSD
    Genode
    GHCJS
    Linux
    MMIXware
    NetBSD
    none
    OpenBSD
    Redox
    Solaris
    WASI
    Windows
Show all
  • aarch64-darwin
  • aarch64-genode
  • aarch64-linux
  • aarch64-netbsd
  • aarch64-none
  • aarch64_be-none
  • arm-none
  • armv5tel-linux
  • armv6l-linux
  • armv6l-netbsd
  • armv6l-none
  • armv7a-darwin
  • armv7a-linux
  • armv7a-netbsd
  • armv7l-linux
  • armv7l-netbsd
  • avr-none
  • i686-cygwin
  • i686-darwin
  • i686-freebsd
  • i686-genode
  • i686-linux
  • i686-netbsd
  • i686-none
  • i686-openbsd
  • i686-windows
  • javascript-ghcjs
  • loongarch64-linux
  • m68k-linux
  • m68k-netbsd
  • m68k-none
  • microblaze-linux
  • microblaze-none
  • microblazeel-linux
  • microblazeel-none
  • mips-linux
  • mips-none
  • mips64-linux
  • mips64-none
  • mips64el-linux
  • mipsel-linux
  • mipsel-netbsd
  • mmix-mmixware
  • msp430-none
  • or1k-none
  • powerpc-netbsd
  • powerpc-none
  • powerpc64-linux
  • powerpc64le-linux
  • powerpcle-none
  • riscv32-linux
  • riscv32-netbsd
  • riscv32-none
  • riscv64-linux
  • riscv64-netbsd
  • riscv64-none
  • rx-none
  • s390-linux
  • s390-none
  • s390x-linux
  • s390x-none
  • vc4-none
  • wasm32-wasi
  • wasm64-wasi
  • x86_64-cygwin
  • x86_64-darwin
  • x86_64-freebsd
  • x86_64-genode
  • x86_64-linux
  • x86_64-netbsd
  • x86_64-none
  • x86_64-openbsd
  • x86_64-redox
  • x86_64-solaris
  • x86_64-windows