Влияние на стиль GHC -Wall
Считается хорошей практикой, чтобы включить предупреждения GHC с помощью -Wall
. Однако я обнаружил, что исправление этих предупреждений отрицательно влияет на некоторые типы кодовых конструкций.
Пример 1:
Использование эквивалента do-notation для 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
в конце концов?
Ответы
Ответ 1
Пример 1:
Я переучился писать парсеры в аппликативном стиле - они гораздо более сжатые. Например, вместо:
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, человек.
Ответ 2
Я думаю, что использование -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)
не является приятной.
Подписи типов требуются для всех верхних уровней
При написании быстрого и грязного кода это раздражает. Для простого кода, где используется не более одного или двух типов данных (для exapmle, я знаю, что я работаю только с 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
в Haskell...
Существует больше таких случаев, когда числовой код может стать нечитаемым, чтобы выполнить требования -Wall
. Но я все еще использую -Wall
для кода, на который я хотел бы быть уверен.
Ответ 3
Я бы порекомендовал продолжать использовать параметр -Wall в качестве параметра по умолчанию и отключить любые проверки, которые вам нужны, на локальном, для каждого модуля, используя прагму OPTIONS_GHC в верхней части соответствующих файлов.
Я мог бы сделать исключение для действительно "-fno-warn-unused-do-bind", но одно предложение может заключаться в использовании явной функции "void"... запись "void f" кажется более приятной, чем '_ < - f'.
Что касается тенирования имени - я думаю, что вообще полезно избегать, если вы можете - увидеть "карту" в середине некоторого кода, приведет большинство Haskellers к ожидаемой стандартной библиотеке fn.
Ответ 4
Затенение имени может быть довольно опасным. В частности, может возникнуть трудность рассуждать о том, в какой области вводится имя.
Неиспользованный шаблон привязывается, но обозначение не так плохо, но может указывать на то, что используется менее эффективная функция, чем это необходимо (например, mapM
вместо mapM_
).
Как отметил BenMos, использование void
или ignore
для явного отказа от неиспользуемых значений - хороший способ быть явным в отношении вещей.
Было бы неплохо иметь возможность отключать предупреждения только для части кода, а не для всего сразу. Кроме того, флаги cabal и флаги ghc командной строки имеют приоритет над флагами в файле, поэтому я не могу иметь -Wall по умолчанию везде и даже просто просто отключить его для всего одного файла.
Ответ 5
Существует также гораздо менее интрузивная опция -W
, которая позволяет набор разумных предупреждений, в основном связанных с общим стилем кодирования (неиспользуемые импорты, неиспользуемые переменные, неполные совпадения шаблонов и т.д.).
В частности, это не включает в себя два предупреждения, которые вы упомянули.
Ответ 6
Все эти предупреждения помогают предотвратить ошибки и их следует уважать, а не подавлять.
Если вы хотите определить функцию с именем из Prelude, вы можете скрыть ее с помощью
импортировать скрытие прелюдии (карта)
Синтаксис "Скрытие" должен использоваться только для Prelude и модулей одного и того же пакета, иначе вы рискуете поломкой кода с помощью изменений API в импортированном модуле.
Смотрите: http://www.haskell.org/haskellwiki/Import_modules_properly