Подтвердить что ты не робот

Влияние на стиль 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 в конце концов?

4b9b3361

Ответ 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