ghcid for Web App Development

2019-04-07

One of the downsides of developing web applications in Haskell is the long recompilation cycle. While working on code, it is convenient to have an editor open in one window and a browser open in another. When making changes to your Haskell code, it is nice to be able to immediately flip to your browser and interact with the updated web application.

However, long compile times for Haskell prevent this from working smoothly. Running stack build && stack exec mywebapp every time you make a change to your Haskell code is annoying. It takes time for stack build to finish. It also doesn't happen automatically on file change.

ghcid can be used to make this process much quicker and smoother.

ghcid

ghcid is used to quickly reload your Haskell code every time it changes. It loads your Haskell project in a GHCi session and uses the GHC API to recompile your source code on every file change.

Normally, ghcid is used like the following (for a stack project):

$ ghcid --command 'stack ghci'

This runs stack ghci to load your code in GHCi. Then, on each file change, ghcid automatically runs the GHCi command :reload to recompile and reload all your Haskell modules. If there is a compilation error, it is reported by ghcid.

Running :reload within GHCi is very fast, so using ghcid for development is quite an improvement over manually re-running stack build.

However, the above command doesn't automatically run your web application if compilation succeeds.

--test Flag

Despite its name, the --test flag can be used for running your web application whenever compilation succeeds.

The --test flag takes any valid GHCi expression as an argument. There are two common expressions that are used.

  1. Any valid Haskell expression. A function with the type IO () that runs your web application is usually used. For instance, if you have a function defaultMain :: IO () in your codebase, you can run that function whenever your haskell code compiles correctly with the following command:

    $ ghcid --command 'stack ghci' --test 'defaultMain'
  2. Any valid GHCi command. For instance, the GHCi command :main runs the main :: IO () function defined in your Haskell project. You can use the following command to do this:

    $ ghcid --command 'stack ghci' --test ':main'

ghcid will run the expression you have passed to --test if compilation succeeds. When ghcid detects a change in one of your Haskell files, it will kill the expression passed to --test, recompile the code, then re-run the expression.

This is perfect for use with web applications. As long as your code compiles correctly, your web application will always be running. Recompilation is very quick, so you should almost always be able to access it from the browser.

Running Despite GHC Warnings

By default, GHCi doesn't re-run the expression passed to --test if there are GHC compilation warnings. This is sometimes inconvenient. In order to get around this, you can use the --warnings flag. This forces ghcid to run the expression even if there are still GHC warnings.

Here is an example of using the --warnings flag:

$ ghcid --command 'stack ghci' --test 'defaultMain' --warnings

Running Tests

Normally, the --test flag is used to actually run tests as long as compilation has succeeded.

Here's an example of how you can do this with stack:

$ ghcid --command 'stack ghci --test --main-is mypackage:mypackage-test' --test ':main' --warnings

The stack ghci --test --main-is mypackage:mypackage-test command runs GHCi using stack in the Haskell package called mypackage. This assumes you have a test suite defined in your .cabal file called mypackage-test.

The full ghcid command will re-run the main :: IO () function defined in the mypackage-test test suite whenever there is a change in any of your Haskell files.

There are a couple other often-used methods for re-building and running a web application on file change.

If you're using stack, the easiest method is probably as follows:

$ stack build --fast --file-watch --exec "bash -c 'pkill backend; stack exec backend &'"

This assumes you have an executable defined in your .cabal called backend.

This is easy, but stack build --fast is normally slower than reloading in GHCi.

Conclusion

ghcid is a nice command for a fast feedback loop when doing Haskell development. It allows for (mostly) stress-free development of web applications in Haskell.

tags: haskell