# Monadic Bang [![Run Tests](https://github.com/JakobBruenker/monadic-bang/actions/workflows/haskell.yml/badge.svg?branch=main&event=push)](https://github.com/JakobBruenker/monadic-bang/actions/workflows/haskell.yml) This is a GHC Parser plugin for GHC 9.4 and above, intended to make monadic code within `do`-blocks more concise and nicer to work with. Works with HLS. This is heavily inspired by [Idris's !-notation](https://idris2.readthedocs.io/en/latest/tutorial/interfaces.html#notation), but with some [important differences](#comparison-with-idriss--notation). ## Contents 1. [Motivating Examples](#motivating-examples) 2. [Usage](#usage) 3. [Cute Things](#cute-things) 4. [Caveats](#caveats) 5. [Details](#details) 6. [Comparison with Idris's `!`-notation](#comparison-with-idriss--notation) ## Motivating Examples Let's look at a few examples where Haskell syntax can be a bit annoying when it comes to monads - and what this plugin allows you to write instead: When you use `Reader` or `State`, you will often have to use `<-` to bind fairly simple expressions: ```haskell launchMissile :: StateT Int IO () launchMissile = do count <- get liftIO . putStrLn $ "Missile no. " <> show count <> " has been launched" modify' (+ 1) ``` ```haskell help :: Reader Config String help = do manualLink <- asks (.links.manual) email <- asks (.contact.email) pure $ "You can find help by going to " <> manualLink <> " or writing us at " <> email ``` With Monadic Bang, you can instead write ```haskell launchMissile :: StateT Int IO () launchMissile = do liftIO . putStrLn $ "Missile no. " <> show !get <> " has been launched" modify' (+ 1) ``` ```haskell help :: Reader Config String help = do pure $ "You can find help by going to " <> (!ask).links.manual <> " or writing us at " <> (!ask).contact.email ``` With `IORefs`, `STRefs`, mutable arrays, and so on, you'll often have to write code that looks like this, having to use somewhat redundant variable names: ```haskell addIORefs :: IORef Int -> IORef Int -> IO Int addIORefs aRef bRef = do a <- readIORef aRef b <- readIORef bRef pure $ a + b ``` With Monadic Bang, you can write ```haskell addIORefs :: IORef Int -> IORef Int -> IO Int addIORefs a b = do pure $ !(readIORef a) + !(readIORef b) ``` Implicit parameter definitions have somewhat more limited syntax than regular definitions: You can't write something like `?foo <- action`. That lead me to have to write this in a Vulkan program: ```haskell initQueues = do let getQueue = getDeviceQueue ?device graphicsQueue <- getQueue ?graphicsQueueFamily 0 presentQueue <- getQueue ?presentQueueFamily 0 computeQueue <- getQueue ?computeQueueFamily 1 let ?graphicsQueue = graphicsQueue ?presentQueue = presentQueue ?computeQueue = computeQueue pure Dict ``` with Monadic Bang, I can write ```haskell initQueues = do let getQueue = getDeviceQueue ?device let ?graphicsQueue = !(getQueue ?graphicsQueueFamily 0) ?presentQueue = !(getQueue ?presentQueueFamily 0) ?computeQueue = !(getQueue ?computeQueueFamily 1) pure Dict ``` Take this (slightly adapted) code used for the test suite of this very plugin: ```haskell settings :: MonadIO m => m Settings settings = ... -- some long function body initialDynFlags :: MonadIO m => m DynFlags initialDynFlags = do settings' <- settings dflags <- defaultDynFlags settings' llvmConfig pure $ dflags{generalFlags = addCompileFlags $ generalFlags dflags} ``` With this plugin, I can instead write ```haskell settings :: MonadIO m => m Settings settings = ... -- some long function body initialDynFlags :: MonadIO m => m DynFlags initialDynFlags = do dflags <- defaultDynFlags !settings llvmConfig pure $ dflags{generalFlags = addCompileFlags $ generalFlags dflags} ``` Or, to take some more code from this plugin's implementation ```haskell do logger <- getLogger liftIO $ logMsg logger MCInfo (UnhelpfulSpan UnhelpfulNoLocationInfo) m ``` Why have `logger` *and* `getLogger` when you can instead write ```haskell do liftIO $ logMsg !getLogger MCInfo (UnhelpfulSpan UnhelpfulNoLocationInfo) m ``` The pattern you might have noticed here is that this plugin is convenient whenever you have a `do`-block with a `<-` that doesn't do pattern matching, whose bound variable is only used once, and has a short right-hand side. While that might sound like a lot of qualifiers, it does occur fairly often in practice. ## Usage To use this plugin, you have to add `monadic-bang` to the `build-depends` stanza in your `.cabal` file. Then you can either add `-fplugin=MonadicBang` to the `ghc-options` stanza, or add ```haskell {-# OPTIONS_GHC -fplugin=MonadicBang #-} ``` to the top of the files you want to use it in. This should also allow HLS to pick up on the plugin, as long as you use HLS 1.9.0.0 or above. The plugin supports a couple of options, which you can provide via invocations of `-fplugin-opt=MonadicBang: