🎉I am writing these notes at Brick, a magical mystery no-bullshit publishing platform. Turns out writing goes much faster when I don't have to hit “Publish” or do
git commit
.You can use it too — check it out at Brick.do.
Previously I thought recursive imports (via .hs-boot
files) were cumbersome, hard, broken, required machinations with Cabal, etc.
I just used them in anger and it turns out they are super easy.
Let's say you have two modules that use each other.
Expressions can include patterns:
module AST.Expr where
import AST.Pat
data Expr
= ...
| ExprLet (Pat, Expr) Expr -- let ... = ... in ...
deriving (Eq, Ord, Show)
Patterns can include expressions:
module AST.Pat where
import AST.Expr
data Pat
= ...
| PatViewPattern Expr Pat -- (... -> ...)
deriving (Eq, Ord, Show)
And then you get the famous “module imports form a cycle” error. Oh shit.
.hs-boot files to the rescue!
Step one: add {-# SOURCE #-}
to some of the recursive imports. Not necessarily all — just enough to break the cycles.
import {-# SOURCE #-} AST.Pat
Step two: provide just enough information in the corresponding .hs-boot
file. Only the things you actually use. Type definitions are not necessary — only their names. Instance contents aren't necessary — only their headers.
-- AST/Pat.hs-boot
module AST.Pat where
data Pat
instance Eq Pat -- 'deriving' doesn't work, you have to write it like this
instance Ord Pat
instance Show Pat
That's it. You're done. If you have more things you need to include — functions, classes, etc — read the documentation.