Влияние на стиль GHC -Wall - PullRequest
       25

Влияние на стиль GHC -Wall

24 голосов
/ 13 ноября 2010

Хорошей практикой считается включение предупреждений GHC с помощью -Wall.Однако я обнаружил, что исправление этих предупреждений оказывает негативное влияние на некоторые типы конструкций кода.

Пример 1:

При использовании эквивалента do-обозначения 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 ]

25 голосов
/ 14 ноября 2010

Пример 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 работать не весело. В любом случае, держите свои модули небольшими, а абстракции - тесными, и это не должно быть проблемой. Рассматривайте это как предупреждение, которое говорит упрощенией, человек .

10 голосов
/ 15 ноября 2010

Я думаю, что использование -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))

И все знают, как часто нужно fromIntegral в Хаскеле ...


В большинстве подобных случаев числовой код может стать нечитаемым только для выполнения требований -Wall. Но я все еще использую -Wall в коде, в котором хотел бы быть уверен.

7 голосов
/ 14 ноября 2010

Я бы порекомендовал продолжать использовать '-Wall' в качестве опции по умолчанию и отключить любые проверки, которые вам нужны, на локальной основе для каждого модуля, используя прагму OPTIONS_GHC в верхней части соответствующих файлов.

я могу сделать исключение для '-fno-warn-unused-do-bind', но одно из предложений может состоять в том, чтобы использовать явную функцию 'void' ... запись 'void f' кажется лучше, чем '_ <-f '. </p>

Что касается теней имен - я думаю, что, как правило, лучше избегать, если вы можете - просмотр карты в середине некоторого кода приведет к тому, что большинство Haskellers будут ожидать стандартную библиотеку fn.

6 голосов
/ 14 ноября 2010

Затенение имени может быть довольно опасным. В частности, может стать трудно рассуждать о том, в какой области вводится имя.

Неиспользуемые привязки шаблона в нотации do не так плохи, но могут указывать на то, что используется менее эффективная функция, чем необходимо (например, mapM вместо mapM_).

Как указывал BenMos, использование void или ignore для явного отбрасывания неиспользуемых значений является хорошим способом явно заявить о вещах.

Было бы неплохо иметь возможность отключать предупреждения только для части кода, а не для всего сразу. Кроме того, флаги cabal и флаги ghc командной строки имеют приоритет над флагами в файле, поэтому я не могу иметь -Wall по умолчанию везде и даже легко просто отключить его для всего одного файла.

4 голосов
/ 04 марта 2012

Существует также гораздо менее навязчивая опция -W, которая включает набор разумных предупреждений, в основном связанных с общим стилем кодирования (неиспользуемый импорт, неиспользуемые переменные, неполные совпадения с образцами и т.в частности, оно не включает два упомянутых вами предупреждения.

3 голосов
/ 21 ноября 2010

Все эти предупреждения помогают предотвратить ошибки и должны соблюдаться, а не подавляться.Если вы хотите определить функцию с именем из Prelude, вы можете скрыть ее, используя

import Prelude hiding (map)

Синтаксис «Hiding» следует использовать только для Prelude и модулейтот же пакет, в противном случае вы рискуете разбить код из-за изменений API в импортированном модуле.

См .: http://www.haskell.org/haskellwiki/Import_modules_properly

...