Impact on style of GHC -Wall

前端 未结 6 426
囚心锁ツ
囚心锁ツ 2021-02-02 10:16

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 c

相关标签:
6条回答
  • 2021-02-02 10:36

    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

    0 讨论(0)
  • 2021-02-02 10:43

    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 <$> 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.

    0 讨论(0)
  • 2021-02-02 10:44

    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.

    0 讨论(0)
  • 2021-02-02 10:46

    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.

    0 讨论(0)
  • 2021-02-02 10:50

    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.

    0 讨论(0)
  • 2021-02-02 10:54

    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.

    0 讨论(0)
提交回复
热议问题