Как «отладить» Haskell с помощью printfs?

из сообщества Ocaml, я пытаюсь немного изучить Haskell. Переход проходит довольно хорошо, но я немного запутался с отладкой. Я использовал (много) «printf» в своем коде ocaml, чтобы проверить некоторые промежуточные значения, или в качестве флага, чтобы увидеть, где вычисление точно провалилось.

Поскольку printf является действием IO , я должен поднять весь мой код haskell внутри монады IO , чтобы иметь возможность такого рода отладки? Или есть лучший способ сделать это (я действительно не хочу делать это вручную, если этого можно избежать)

Я также нахожу функцию trace : http://www.haskell.org/haskellwiki/Debugging#Printf_and_friends что кажется именно тем, что я хочу, но я не понимаю его тип: нигде нет IO ! Может кто-нибудь объяснить мне, как работает функция трассировки?

61
задан Andrew Grimm 20 October 2010 в 22:44
поделиться

5 ответов

trace - самый простой в использовании метод отладки. Это не в IO точно по той причине, которую вы указали: нет необходимости поднимать ваш код в монаде IO . Это реализовано так

trace :: String -> a -> a
trace string expr = unsafePerformIO $ do
    putTraceMsg string
    return expr

Итак, за кулисами есть ввод-вывод, но для выхода из него используется unsafePerformIO . Это функция, которая потенциально нарушает ссылочную прозрачность, о чем вы можете догадаться, глядя на ее тип IO a -> a , а также ее имя.

54
ответ дан 24 November 2019 в 17:22
поделиться

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

Однако, на свой страх и риск, вы можете скомпоновать IO a -> a, т.е. выполнить нечистый IO. Это хак и, конечно, "страдает" от ленивой оценки, но это то, что делает trace просто для отладки.

Тем не менее, для отладки, вероятно, следует идти другими путями:

  1. Снижение необходимости отладки промежуточных значений

    • Пишите небольшие, многократно используемые, понятные, общие функции, правильность которых очевидна.
    • Объединяйте правильные части в более правильные части.
    • Пишите тесты или пробуйте части в интерактивном режиме.
  2. Используйте точки останова и т.д. (отладка на основе компилятора)

  3. Используйте общие монады. Если ваш код все же монадический, пишите его независимо от конкретной монады. Используйте тип M a = ... вместо обычного IO ... . После этого вы можете легко объединить монады с помощью трансформаторов и наложить на них отладочную монаду. Даже если необходимость в монадах отпадет, можно просто вставить Identity a для чистых значений.

17
ответ дан 24 November 2019 в 17:22
поделиться

Ну, поскольку весь Haskell построен на принципе ленивой оценки (так что порядок вычислений фактически недетерминирован), использование printf'ов имеет в нем очень мало смысла.

Если REPL+инспекция результирующих значений действительно недостаточны для отладки, то обернуть все в IO - единственный выбор (но это не ПРАВИЛЬНЫЙ путь программирования на Haskell).

-4
ответ дан 24 November 2019 в 17:22
поделиться

Как бы то ни было, здесь есть два вида «отладки»:

  • Регистрация промежуточных значений, таких как значение, которое конкретное подвыражение имеет при каждом вызове в рекурсивная функция
  • Проверка поведения вычисления выражения во время выполнения

В строгом императивном языке они обычно совпадают. В Haskell они часто не делают этого:

  • Запись промежуточных значений может изменить поведение во время выполнения, например, путем принудительного вычисления терминов, которые в противном случае были бы отброшены.
  • Фактический процесс вычисления может резко отличаться от кажущейся структуры выражения из-за лени и общих подвыражений.

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

Также обычно не требуется помещать все в монаду, только функции, которые должны записывать в значение «log» - например, вы можете вынести только те части выражения, которые могут потребоваться выполните регистрацию, оставив основную логику чистой, затем заново соберите все вычисления, объединив чистые функции и записывая вычисления обычным способом с помощью fmap и т. д. Имейте в виду, что Writer - это своего рода извинение для монады: без возможности читать из журнала, только запись в него, каждое вычисление логически не зависит от его контекста, что упрощает манипулирование вещами.

Но в некоторых случаях даже это излишество - для многих чистых функций достаточно просто перенести подвыражения на верхний уровень и попробовать что-то в REPL.

Однако, если вы действительно хотите проверить поведение чистого кода во время выполнения - например, чтобы выяснить, почему подвыражение расходится - в целом нет способа сделать это из другого чистого кода - по сути, это определение чистоты. Так что в этом случае у вас нет другого выбора, кроме как использовать инструменты, которые существуют «вне» чистого языка: либо нечистые функции, такие как unsafePerformPrintfDebugging - эррр, я имею в виду trace - либо модифицированная среда выполнения, такая как отладчик GHCi.

14
ответ дан 24 November 2019 в 17:22
поделиться

trace также имеет тенденцию переоценивать свои аргументы в пользу печати, теряя при этом многие преимущества лени.

2
ответ дан 24 November 2019 в 17:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: