The Haskell M-Word Is a Type-Based Macro System

2020-12-24
haskell

Typeclass is great to reduce boilerplate.

In Racket, I define macros to chain computation. For example, in my latest automation, I define a chain operation to chain a series of git commands.

(result~> : (Result String String)
   (sh "git add -A -N" (Some pwd))
   (sh "git add -u" (Some pwd))
   (sh "git status -s" (Some pwd))
   (let ([text (string-trim _)])
     (if (equal? text "")
         NO-DEST-CHANGES-RESULT
         (Ok text)))
   (sh (format "git commit -m '~a'" _) (Some pwd))
   (sh "git push origin master" (Some pwd)))))

My result~> macro in Typed Racket.

There are three cool features of the result~>:

  1. The operations are short circuit by any one of the expressions that returns (Error stderr)
  2. If one operation succeeds, the next operation can retrieve the previous stdout using _
  3. It’s type safe; all expression needs to return Result type within the body of result~>

Well, in Haskell the do-notation seems great for reducing boilerplate within context. What’s cooler than the Racket macro is that I can write different things for different context, and from the syntax level, they look exactly the same. Below I implement a CommandM to carry IO computation, and any failures short-circuit the entire computation.

newtype CommandM a = CommandM (IO (Either String a))

upload :: String -> CommandM String
upload pwd = do
  _ <- sh "git" ["add", "-A", "-N"] (Just pwd)
  _ <- sh "git" ["add", "-u"] (Just pwd)
  text <- sh "git" ["status", "-s"] (Just pwd)
  msg <- if text == ""
    then CommandM $ pure $ Left "No changes"
    else pure text
  _ <- sh "git" ["commit", "-m", msg] (Just pwd)
  sh "git" ["push", "origin", "master"] (Just pwd)