Comment evaluation

With the --enable-eval flag set, ghciwatch will execute Haskell code in comments which start with $> in GHCi.

myGreeting :: String
myGreeting = "Hello"

-- $> putStrLn (myGreeting <> " " <> myGreeting)

Prints:

• src/MyLib.hs:9:7: putStrLn (myGreeting <> " " <> myGreeting)
Hello Hello

Running tests with eval comments

Eval comments can be used to run tests in a single file on reload. For large test suites (thousands of tests), this can be much faster than using Hspec’s --match option, because --match has to load the entire test suite and perform string matches on [Char] to determine which tests should be run. (Combine this with Cabal’s --repl-no-load option to only load the modules your test depends on for even faster reloads.)

module MyLibSpec (spec) where

import Test.Hspec
import MyLib (myGreeting)

-- $> import Test.Hspec  -- May be necessary for some setups.
-- $> hspec spec

spec :: Spec
spec = do
  describe "myGreeting" $ do
    it "is hello" $ do
      myGreeting `shouldBe` "Hello"

Grammar

Single-line eval comments have the following grammar:

[ \t]*     # Leading whitespace
"-- $>"    # Eval comment marker
[ \t]*     # Optional whitespace
[^\n]+ \n  # Rest of line

Multi-line eval comments have the following grammar:

[ \t]*        # Leading whitespace
"{- $>"       # Eval comment marker
([ \t]* \n)?  # Optional newline
([^\n]* \n)*  # Lines of Haskell code
[ \t]*        # Optional whitespace
"<$ -}"       # Eval comment end marker

Performance implications

Note that because each loaded module must be read (and re-read when it changes) to parse eval comments, enabling this feature has some performance overhead. (It’s probably not too bad, because all those files are in your disk cache anyways from being compiled by GHCi.)