对 GHC -Wall 风格的影响

发布于 2024-10-01 19:02:15 字数 915 浏览 1 评论 0原文

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

示例 1:

如果我没有明确使用 _ <- f<,则使用与 f >> 等效的 do 表示法将生成警告/code> 形式:

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 吗?

It is considered good practice to enable GHC warnings with -Wall. However, I've found out that fixing those warnings has a negative effect for some types of code constructs.

Example 1:

Using the do-notation equivalent of f >> will generate a warning if I don't explicitly use the _ <- f form:

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

I understand that I can forget to do something with the result of f. However, it is legitimate to ignore the result (very common in parsers). There is no warning when using >>, right? Using _ <- is heavier than it should.

Example 2:

Naming a pattern variable with the same name of a visible function will give:

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

This is getting worse when using record syntax as namespace gets polluted quickly. The solution is to give an alternate name in the pattern expression. So I end up using a less appropriate name just to avoid a warning. I don't feel it's a good-enough reason.

I know I can use -fno-warn-... options but should I stick with -Wall after all?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

迷荒 2024-10-08 19:02:15

示例 1:

我重新学习了以应用风格编写解析器——它们更加简洁。例如,而不是:

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

我改为写:

funCallExpr :: Parser AST
funCallExpr = FunCall <
gt; 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,伙计

Example 1:

I have re-learned to write parsers in Applicative style -- they are much more concise. Eg, instead of:

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

I instead write:

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

But what can I say, if you don't like the warning, disable it as it suggests.

Example 2:

Yeah I find that warning a bit irritating as well. But it has saved me a couple times.

It ties into naming conventions. I like to keep modules pretty small, and keep most imports qualified (except for "notation" imports like Control.Applicative and Control.Arrow). That keeps the chances of name conflict low, and it just makes things easy to work with. hothasktags makes this style tolerable if you are using tags.

If you are just pattern matching on a field with the same name, you can use -XNamedFieldPuns or -XRecordWildCards to reuse the name:

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

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

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

Another common convention is to add a hungarian prefix to record labels:

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

But yeah, records are no fun to work with in Haskell. Anyway, keep your modules small and your abstractions tight and this shouldn't be a problem. Consider it as a warning that says simplifyyyy, man.

不…忘初心 2024-10-08 19:02:15

我认为使用 -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 ),在任何地方编写类型签名可能会使阅读变得复杂。有两个警告只是因为缺少类型签名:

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

I think that use of -Wall may lead to less readable code. Especially, if it is doing some arithmetics.

Some other examples, where the use of -Wall suggests modifications with worse readability.

(^) with -Wall requires type signatures for exponents

Consider this code:

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

With -Wall it gives two warnings like this:

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)

Writing (^(2::Int) everywhere instead of (^2) is not nice.

Type signatures are required for all top-levels

When writing quick and dirty code, it's annoying. For simple code, where there are at most one or two data types in use (for exapmle, I know that I work only with Doubles), writing type signatures everywhere may complicate reading. In the example above there are two warnings just for the lack of type signature:

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

As a distraction, one of them refers to the line different from the one where the type signature is needed.

Type signatures for intermediate calculations with Integral are necessary

This is a general case of the first problem. Consider an example:

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

It gives a bunch of warnings. Everywhere with fromIntegral to convert back to 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))

And everyone knows how often one needs fromIntegral in Haskell...


There are more cases like these the numeric code risks to become unreadable just to fulfill the -Wall requirements. But I still use -Wall on the code I'd like to be sure of.

茶底世界 2024-10-08 19:02:15

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

我可能会例外的确实是“-fno-warn-unused-do-bind”,但一个建议可能是使用显式的“void”函数......写“void f”似乎比“_ <”更好;-f'。

至于名称隐藏 - 我认为如果可以的话最好避免 - 在某些代码中间看到“map”将导致大多数 Haskellers 期望标准库 fn。

I would recommend continuing to use '-Wall' as the default option, and disable any checks you need to on local, per-module basis using an OPTIONS_GHC pragma at the top of relevant files.

The one I might make an exception for is indeed '-fno-warn-unused-do-bind', but one suggestion might be to use an explicit 'void' function ... writing 'void f' seems nicer than '_ <- f'.

As for name shadowing - I think it's generally good to avoid if you can - seeing 'map' in the middle of some code will lead most Haskellers to expect the standard library fn.

郁金香雨 2024-10-08 19:02:15

名称隐藏可能非常危险。特别是,很难推断名称被引入的范围。

do 表示法中未使用的模式绑定并没有那么糟糕,但可能表明正在使用效率较低的函数(例如 mapM而不是mapM_)。

正如 BenMos 指出的那样,使用 voidignore 显式丢弃未使用的值是一种明确表达事物的好方法。

如果能够仅对一段代码而不是同时对所有代码禁用警告,那就太好了。另外,cabal 标志和命令行 ghc 标志优先于文件中的标志,因此我不能在任何地方默认使用 -Wall,甚至可以轻松地为整个单个文件禁用它。

Name shadowing can be quite dangerous. In particular, it can become difficult to reason about what scope a name is introduced in.

Unused pattern binds in do notation are not as bad, but can indicate that a less efficient function than necessary is being used (e.g. mapM instead of mapM_).

As BenMos pointed out, using void or ignore to explicitly discard unused values is a nice way to be explicit about things.

It would be quite nice to be able to disable warnings for just a section of code, rather than for everything at once. Also, cabal flags and command line ghc flags take precedence over flags in a file, so I can't have -Wall by default everywhere and even easily just disable it for the entirety of a single file.

又爬满兰若 2024-10-08 19:02:15

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

import Prelude hide (map)

隐藏它。 “隐藏”语法只能用于 Prelude 和同一包的模块,否则您将面临因 API 更改而导致代码损坏的风险。导入的模块。

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

All these warnings help to prevent mistakes and should be respected, not suppressed.
If you want to define a function with a name from Prelude, you can hide it using

import Prelude hiding (map)

'Hiding' syntax should only be used for Prelude and modules of the same package, otherwise you risk code breakage by API changes in the imported module.

See: http://www.haskell.org/haskellwiki/Import_modules_properly

‖放下 2024-10-08 19:02:15

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

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

There is also the much less intrusive -W option, which enables a set of reasonable warnings mostly related to general coding style (unused imports, unused variables, incomplete pattern matches, etc.).

In particular it does not include the two warnings you mentioned.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文