对GHC-Wall风格的影响

gaw*_*awi 24 warnings haskell coding-style compiler-warnings ghc

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

例1:

f >>如果我没有明确使用_ <- f表单,使用等号的do- not会产生警告:

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
Run Code Online (Sandbox Code Playgroud)

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

例2:

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

Warning: This binding for `map' shadows the existing binding
           imported from Prelude
Run Code Online (Sandbox Code Playgroud)

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

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

luq*_*qui 25

例1:

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

funCallExpr :: Parser AST
funCallExpr = do
    func <- atom
    token "("
    arg <- expr
    token ")"
    return $ FunCall func arg
Run Code Online (Sandbox Code Playgroud)

我反而写道:

funCallExpr :: Parser AST
funCallExpr = FunCall <$> atom <* token "(" <*> expr <* token ")"
Run Code Online (Sandbox Code Playgroud)

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

例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
Run Code Online (Sandbox Code Playgroud)

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

data Foo = Foo { fooBaz :: Int, fooBar :: String }
Run Code Online (Sandbox Code Playgroud)

但是,在Haskell中使用记录并不好玩.无论如何,保持你的模块小,你的抽象紧,这应该不是问题.将它视为一种警告,即简化,男人.

  • 我来自OOP世界所以使用记录语法对我来说是很自然的.我不清楚如何重新连接我的大脑去做其他事情.也许这将是StackOverflow的另一个问题... (2认同)

sas*_*nin 10

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

其他一些例子,其中使用-Wall暗示修改具有更差的可读性.

(^)-Wall需要的指数型签名

考虑以下代码:

norm2 x y = sqrt (x^2 + y^2)
main = print $ norm2 1 1
Run Code Online (Sandbox Code Playgroud)

-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)
Run Code Online (Sandbox Code Playgroud)

(^(2::Int)到处写,而(^2)不是很好.

所有顶级都需要类型签名

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

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
Run Code Online (Sandbox Code Playgroud)

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

用于中间计算的类型签名Integral是必要的

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

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

它给出了一堆警告.无处不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))
Run Code Online (Sandbox Code Playgroud)

每个人都知道fromIntegralHaskell 需要多久一次......


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


小智 7

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

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

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


scl*_*clv 6

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

在符号中使用未使用的模式并不是那么糟糕,但可以表明使用了比必要的效率低的功能(例如,mapM代替mapM_).

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

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

  • `void`是4个字符长.`_ <-`也是4个字符.前者需要导入,后者则由语法支持.`_ <-`已经是一个常见的习语.如果我必须在`_ <-`和`void`之间做出选择,我宁愿选择`_ <-` (3认同)