使用-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
吗?
答案 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.Applicative
和Control.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指出的那样,使用void
或ignore
明确地丢弃未使用的值是明确事物的好方法。
能够仅为一段代码禁用警告,而不是一次性禁用所有内容,这将是非常好的。此外,cabal标志和命令行ghc标志优先于文件中的标志,因此我不能在任何地方默认使用-Wall,甚至可以轻松地为整个单个文件禁用它。
答案 4 :(得分:4)
还有一个更少侵入性-W
选项,它允许一组合理的警告,主要与一般编码风格(未使用的导入,未使用的变量,不完整的模式匹配等)相关。
特别是它不包括您提到的两个警告。
答案 5 :(得分:3)
所有这些警告都有助于防止错误,应该得到尊重,而不是被压制。 如果要使用Prelude中的名称定义函数,可以使用
隐藏它导入Prelude隐藏(地图)
'隐藏'语法只应用于Prelude和同一个包的模块,否则您可能会因导入模块中的API更改而导致代码破坏。
请参阅:http://www.haskell.org/haskellwiki/Import_modules_properly