How to avoid extra indentation in Haskell template quotes?

I have a toy program:

$ cat a.hs main = putStrLn "Toy example" $ runghc a.hs Toy example 

Add a Template Haskell template to it:

 $ cat b.hs {-# LANGUAGE TemplateHaskell #-} id [d| main = putStrLn "Toy example" |] $ runghc b.hs b.hs:3:0: parse error (possibly incorrect indentation) 

In this case, correct the indent:

 $ cat c.hs {-# LANGUAGE TemplateHaskell #-} id [d| main = putStrLn "Toy example" |] $ runghc c.hs Toy example 

One space is enough, but I need to back off both trailing lines.

Can I give up most of my module? (My real modules have much more than one line of code.) (And without using the notation { ; ; } ?)

I want all module declarations to be fixed in a quote - in regular code, I can replace (...) with $ ... , is there any equivalent [d|...|] that would allow me to avoid closed brackets as well as indentation?

Or is there a way that module A can say that the top-level declarations of any module B imported by A are automatically processed by the A export function?

Notes:

  • The Haskell template in my real program is more complex than id - it looks at declarations for variable names that run prop_ , and builds a test suite containing them. Is there some other clean Haskell way that I could do instead, without having to directly iterate over the source files?
  • I am using GHC v6.12.1. When I use GHC v7.0.3, an error for b.hs is reported for another location - b.hs:3:1 - but the behavior is otherwise identical.
+4
source share
2 answers

[my program] looks at declarations for variable names that run prop_, and builds a test suite containing them. Is there any other pure Haskell method that I could do instead, without directly looping through the source files?

Yes there is! Using the language-haskell-extract package .

 {-# LANGUAGE TemplateHaskell #-} import Language.Haskell.Extract import Test.QuickCheck prop_foo xs = reverse (reverse xs) == (xs :: [Int]) prop_bar = 2 + 2 == 4 properties = $(functionExtractorMap "^prop_" [|\name prop -> putStrLn name >> quickCheck prop|]) main = sequence_ properties 

Running this, we get:

 prop_foo +++ OK, passed 100 tests. prop_bar +++ OK, passed 100 tests. 

However, before moving on to a new wheel, I also recommend that you take a look at the test-framework-th package , which does pretty much exactly that, but also supports HUnit and has a good test runner (with colors!).

 {-# LANGUAGE TemplateHaskell #-} import Test.Framework.Providers.HUnit import Test.Framework.Providers.QuickCheck2 import Test.Framework.TH import Test.HUnit import Test.QuickCheck prop_bar = 1+1 == 2 case_foo = 2+2 @?= 4 main = $(defaultMainGenerator) 

Output:

 Main: bar: [OK, passed 100 tests] foo: [OK] Properties Test Cases Total Passed 1 1 2 Failed 0 0 0 Total 1 1 2 

There is also testGroupGenerator , which is useful if you want to combine tests from multiple files.

+3
source

If the test suite is for QuickCheck, I recommend that you use the new All module instead: http://hackage.haskell.org/packages/archive/QuickCheck/2.4.1.1/doc/html/Test-QuickCheck-All.html

It does the same, except that it extracts property names by accessing the file system and analyzing the file in which the splicing is located (if you use some other test environment, you can use the same approach anyway).

If you really want to quote the entire file, you can use a quasicycler (which does not require indentation) instead. You can easily create your request for haskell-src-meta, but I advise against this approach because it will not support some Haskell functions and will probably give bad error messages.


Aggregation of test suits is a difficult problem, you can probably expand the procedure for collecting names to somehow monitor the import, but this is a lot of work. Here is a workaround:

You can use this modified version of forAllProperties :

 import Test.QuickCheck import Test.QuickCheck.All import Language.Haskell.TH import Data.Char import Data.List import Control.Monad allProperties :: Q Exp -- :: [(String,Property)] allProperties = do Loc { loc_filename = filename } <- location when (filename == "<interactive>") $ error "don't run this interactively" ls <- runIO (fmap lines (readFile filename)) let prefixes = map (takeWhile (\c -> isAlphaNum c || c == '_') . dropWhile (\c -> isSpace c || c == '>')) ls idents = nubBy (\xy -> snd x == snd y) (filter (("prop_" `isPrefixOf`) . snd) (zip [1..] prefixes)) quickCheckOne :: (Int, String) -> Q [Exp] quickCheckOne (l, x) = do exists <- return False `recover` (reify (mkName x) >> return True) if exists then sequence [ [| ($(stringE $ x ++ " on " ++ filename ++ ":" ++ show l), property $(mono (mkName x))) |] ] else return [] [|$(fmap (ListE . concat) (mapM quickCheckOne idents)) |] 

You also need the runQuickCheckAll function, which is not exported from All:

 runQuickCheckAll :: [(String, Property)] -> (Property -> IO Result) -> IO Bool runQuickCheckAll ps qc = fmap and . forM ps $ \(xs, p) -> do putStrLn $ "=== " ++ xs ++ " ===" r <- qc p return $ case r of Success { } -> True Failure { } -> False NoExpectedFailure { } -> False 

In each test module, you now define

 propsN = $allProperties 

where N is the number or other unique identifier (or you can use the same name and use the qualified names in the step below).

In your main test suite, you define

 props :: [(String,Property)] props = concat [props1, props2 ... propsN] 

If you really want to avoid adding a list member for each module, you can make a TH script that generates this list.

To run all your tests just say

 runTests = runQuickCheckAll quickCheckResult props 
+4
source

All Articles