13
votes

Coming from C++, I'm used to be able to build simple forms of compile-time assertions, where I could emit warnings or errors during compilation if some simple conditions (e.g. over simple algebraic expressions) weren't met via use of template meta-programming and/or cpp(1)

For instance, if I wanted to make sure my program compiles only when Int has at least a certain minBound/maxBound range or alternatively, if a loss-free (as in reversible) conversion from Int64 to Int is possible with the current compilation target. Is this possible with some GHC Haskell extension? My first guess would have been to use TH. Are there other GHC facilities that could be exploited to this end?

2
(Un)fortunately, there are no dependent types in Haskell, which would have been great for this purpose (but which would likely require you put many more type annotations).Alexandre C.
@Alexandre: Sounds interesting... Could you elaborate on how dependent types could be used to, say, determine whether Int64 fits into an Int at compile-time?hvr
@hvr: you can use an Int lo hi type when lo and hi are bounds on the integer. Now, eg. (+) has type Int l1 h1 -> Int l2 h2 -> Int (l1 + l2) (h1 + h2).Alexandre C.
For the minBound/maxBound check you can also use a configure script.Mikhail Glushenkov
I don't know if you can use it for this purpose, but GHC allows you to run the C preprocessor over your Haskell code with the -XCPP flag (or {-# LANGUAGE CPP #-} directive).Antal Spector-Zabusky

2 Answers

11
votes

Here's a generalized and slightly simplified version of Anthony's example:

{-# LANGUAGE TemplateHaskell #-}
module StaticAssert (staticAssert) where

import Control.Monad (unless)
import Language.Haskell.TH (report)

staticAssert cond mesg = do
    unless cond $ report True $ "Compile time assertion failed: " ++ mesg
    return [] -- No need to make a dummy declaration

Usage:

{-# LANGUAGE TemplateHaskell #-}
import StaticAssert

$(staticAssert False "Not enough waffles")
6
votes

Using TH for this isn't too bad. Here is a module that defines the desired assertion as part of a vestigial declaration:

{-# LANGUAGE TemplateHaskell #-}
module CompileTimeWarning where
import Control.Monad (unless)
import Data.Int (Int64)
import Language.Haskell.TH

assertInt = let test = fromIntegral (maxBound::Int) == (maxBound::Int64)
            in do unless test $ report True "Int is not safe!"
                  n <- newName "assertion"
                  e <- fmap NormalB [|()|]
                  return $ [FunD n [Clause [] e []]]

Using the assertion involves a top-level declaration that isn't used for anything other than the assertion:

{-# LANGUAGE TemplateHaskell #-}
import CompileTimeWarning
$(assertInt)