对GHC-Wall风格的影响

时间:2010-11-13 20:50:36

标签: haskell coding-style warnings compiler-warnings ghc

使用-Wall启用GHC警告被视为良好做法。但是,我发现修复这些警告会对某些类型的代码构造产生负面影响。

示例1:

如果我没有明确使用f >>表格,则使用等同于_ <- f的通知符会产生警告:

Warning: A do-notation statement discarded a result of type Char.
         Suppress this warning by saying "_ <- f",
         or by using the flag -fno-warn-unused-do-bind

我知道我可以忘记对f的结果做些什么。但是,忽略结果是合理的(在解析器中很常见)。使用>>时没有警告,对吧?使用_ <-比它应该更重。

示例2:

使用可见函数的相同名称命名模式变量将给出:

Warning: This binding for `map' shadows the existing binding
           imported from Prelude

使用记录语法时,这会变得更糟,因为命名空间会快速获得污染。解决方案是在模式表达式中提供备用名称。因此,为了避免警告,我最终使用了一个不太合适的名称。我觉得这不是一个足够好的理由。

我知道我可以使用-fno-warn-...选项,但我应该坚持使用-Wall吗?

6 个答案:

答案 0 :(得分:25)

示例1:

我已经重新学会了以Applicative样式编写解析器 - 它们更加简洁。例如,而不是:

funCallExpr :: Parser AST
funCallExpr = do
    func <- atom
    token "("
    arg <- expr
    token ")"
    return $ FunCall func arg

我反而写:

funCallExpr :: Parser AST
funCallExpr = FunCall <$> atom <* token "(" <*> expr <* token ")"

但是我可以说,如果你不喜欢这个警告,请按照它的建议禁用它。

示例2:

是的,我发现这个警告也有点刺激。但它已经救了我几次。

它与命名约定相关联。我希望保持模块非常小,并保持大多数导入合格(除了“注释”导入,如Control.ApplicativeControl.Arrow)。这可以减少名称冲突的可能性,并且只是让事情变得容易。如果您使用标记,则hothasktags可以容忍此样式。

如果您只是在具有相同名称的字段上进行模式匹配,则可以使用-XNamedFieldPuns-XRecordWildCards重复使用该名称:

data Foo = Foo { baz :: Int, bar :: String }

-- RecordWildCards
doubleBaz :: Foo -> Int
doubleBaz (Foo {..}) = baz*baz

-- NamedFieldPuns
reverseBar :: Foo -> String
reverseBar (Foo {bar}) = reverse bar

另一个常见的惯例是添加匈牙利语前缀来记录标签:

data Foo = Foo { fooBaz :: Int, fooBar :: String }

但是,在Haskell中使用记录并不好玩。无论如何,保持你的模块小,你的抽象紧,这应该不是问题。将其视为一个警告,上面写着 simplifyyyy,man

答案 1 :(得分:10)

我认为使用-Wall可能导致代码不太可读。特别是,如果它正在做一些算术。

其他一些例子,使用-Wall表示修改可读性较差。

带有(^)

-Wall需要指数类型签名

考虑以下代码:

norm2 x y = sqrt (x^2 + y^2)
main = print $ norm2 1 1

使用-Wall,它会发出两个警告:

rt.hs:1:18:
    Warning: Defaulting the following constraint(s) to type `Integer'
             `Integral t' arising from a use of `^' at rt.hs:2:18-20
    In the first argument of `(+)', namely `x ^ 2'
    In the first argument of `sqrt', namely `(x ^ 2 + y ^ 2)'
    In the expression: sqrt (x ^ 2 + y ^ 2)

在任何地方写(^(2::Int)代替(^2)并不好。

所有顶级

都需要类型签名

编写快速而脏的代码时,这很烦人。对于简单代码,其中最多使用一种或两种数据类型(例如,我知道我只使用Double s),在任何地方写入类型签名都可能使读取变得复杂。在上面的例子中,只有缺少类型签名的警告有两个:

rt.hs:1:0:
    Warning: Definition but no type signature for `norm2'
             Inferred type: norm2 :: forall a. (Floating a) => a -> a -> a
...

rt.hs:2:15:
    Warning: Defaulting the following constraint(s) to type `Double'
             `Floating a' arising from a use of `norm2' at rt.hs:2:15-23
    In the second argument of `($)', namely `norm2 1 1'
    In the expression: print $ norm2 1 1
    In the definition of `main': main = print $ norm2 1 1

作为一种分心,其中一个是指与需要类型签名的行不同的行。

使用Integral进行中间计算的类型签名是必要的

这是第一个问题的一般情况。考虑一个例子:

stripe x = fromIntegral . round $ x - (fromIntegral (floor x))
main = mapM_ (print . stripe) [0,0.1..2.0]

它提供了一堆警告。在fromIntegral的任何地方转换回Double

rt2.hs:1:11:
    Warning: Defaulting the following constraint(s) to type `Integer'
             `Integral b' arising from a use of `fromIntegral' at rt2.hs:1:11-22
    In the first argument of `(.)', namely `fromIntegral'
    In the first argument of `($)', namely `fromIntegral . round'
    In the expression:
            fromIntegral . round $ x - (fromIntegral (floor x))

每个人都知道在Haskell中需要fromIntegral的频率......


有更多这样的情况,数字代码风险只是为了满足-Wall要求而变得不可读。但我仍然对我想要确定的代码使用-Wall

答案 2 :(得分:7)

我建议继续使用'-Wall'作为默认选项,并使用相关文件顶部的OPTIONS_GHC编译指示禁用本地,每个模块所需的任何检查。

我可能会例外的是'-fno-warn-unused-do-bind',但有一个建议可能是使用一个明确的'void'函数...写'void f'似乎比'_&lt; - f'。

至于名字阴影 - 我认为如果可以的话,通常很好避免 - 在某些代码中间看到“map”会导致大多数Haskeller期望标准库fn。

答案 3 :(得分:6)

名称阴影可能非常危险。特别是,很难推断出引入名称的范围。

符号中未使用的模式绑定并不是那么糟糕,但可以表明使用的函数效率低于必要(例如mapM而不是mapM_)。

正如BenMos指出的那样,使用voidignore明确地丢弃未使用的值是明确事物的好方法。

能够仅为一段代码禁用警告,而不是一次性禁用所有内容,这将是非常好的。此外,cabal标志和命令行ghc标志优先于文件中的标志,因此我不能在任何地方默认使用-Wall,甚至可以轻松地为整个单个文件禁用它。

答案 4 :(得分:4)

还有一个更少侵入性-W选项,它允许一组合理的警告,主要与一般编码风格(未使用的导入,未使用的变量,不完整的模式匹配等)相关。

特别是它不包括您提到的两个警告。

答案 5 :(得分:3)

所有这些警告都有助于防止错误,应该得到尊重,而不是被压制。 如果要使用Prelude中的名称定义函数,可以使用

隐藏它

导入Prelude隐藏(地图)

'隐藏'语法只应用于Prelude和同一个包的模块,否则您可能会因导入模块中的API更改而导致代码破坏。

请参阅:http://www.haskell.org/haskellwiki/Import_modules_properly