2011-10-01 38 views
4

我有一个玩具程序:如何避免模板Haskell声明报价中的额外缩进?

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

让我们添加一些模板哈斯克尔它:

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

b.hs:3:0: parse error (possibly incorrect indentation) 

就在那时,我们进行了修复缩进:

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

一个单一的空间足够,但我必须缩进两条尾部线条。

我可以避免必须缩进大部分模块吗? (我真正的模块比一个单一的代码行等等。)

我想模块声明的所有在报价被捕获(并且不使用{ ; ; }符号?) - 在正常的代码,我可以代替(...)$ ...,是否有一些相当于[d|...|],可以让我避免接近括号和缩进?

或者是有一些方法模块一个可以说,任何模块一个导入到一个功能一个出口自动处理的顶层声明?

注:

  1. 模板哈斯克尔在我实际的程序比id更复杂 - 它扫描的声明为开始prop_变量名,并建立一个包含它们的测试套件。有没有其他纯粹的Haskell方式可以做到这一点,而不需要直接调用源文件?
  2. 我正在使用GHC v6.12.1。当我使用GHC v7.0.3时,b.hs的错误报告给其他位置 - b.hs:3:1 - 但行为本来是相同的。
+0

我不知道为什么GHC上的缩进不断坚持,但为什么不回避问题,使用文件加引号?也许写一个接受一个完整的模块文件,用'module ... where'头部,并吐出它的顶级声明。 –

+0

@ n.m。什么是文件报价器?另外,请记住,您不能使用TH生成导入声明,固定声明和其他一些内容。 –

+0

@JonasDuregård:对不起,我刚刚完成了这个任期。这是你用'quoteFile'和一个准报价单得到的东西。然而,我的印象是,有一个图书馆提供的类似quotaer,就像'[d | ... |]'一样工作,但现在看起来情况并非如此。 –

回答

3

[我的节目]扫描的声明为开始prop_变量名,并建立一个包含它们的测试套件。有没有其他纯粹的Haskell方式可以做到这一点,而不需要直接调用源文件?

是的,有!使用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 

运行此,我们得到:

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

但是,你去重新发明轮子我也建议你看看the test-framework-th package,这确实非常正是这一点,但在此之前也支持HUnit,并有一个不错的测试赛跑者(有颜色!)。

{-# 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) 

输出:

Main: 
    bar: [OK, passed 100 tests] 
    foo: [OK] 

     Properties Test Cases Total  
Passed 1   1   2   
Failed 0   0   0   
Total 1   1   2 

还有一个testGroupGenerator如果你想测试从多个文件合并这是有用的。

+0

'testGroupGenerator'正是我试图写的东西。谢谢。 – dave4420

4

如果测试套件是用于快速检查,我建议你使用新All模块来代替: http://hackage.haskell.org/packages/archive/QuickCheck/2.4.1.1/doc/html/Test-QuickCheck-All.html

它不只是它一样的东西通过访问文件系统和解析获取属性的名称拼接所在的文件(如果您正在使用其他测试框架,您仍然可以使用相同的方法)。

如果您确实想要引用整个文件,则可以使用准标记符(不需要缩进)。你可以很容易地在haskell-src-meta上构建你的报价器,但是我建议采用这种方法,因为它不会支持一些Haskell特性,它可能会给出糟糕的错误消息。


汇总测试服是一个棘手的问题,一个可能会扩展名聚会常规莫名其妙地跟着进口,但它是一个大量的工作。这里有一个解决方法:

您可以使用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 (\x y -> 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)) |] 

您还需要没有从所有导出功能runQuickCheckAll

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 

在每个测试模块现在定义

propsN = $allProperties 

其中N是一些数字或其他唯一标识符(或者您可以在下面的步骤中使用相同的名称和使用限定名称)。

在你的主要测试套件定义

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

如果你真的想避免添加列表成员为每个模块,你可以把生成此列表中的TH脚本。

要运行所有测试,你干脆说

runTests = runQuickCheckAll quickCheckResult props 
+0

我正在使用QuickCheck,这几乎是我正在寻找的东西 - 但我也想将来自不同模块的测试套件汇总到主测试套件中。 Test.QuickCheck.All不会让我这样做。 (尽管如此,我还不知道Test.QuickCheck.All,所以谢谢你告诉我这件事。) – dave4420