Как "отладить" Haskell с помощью printfs? - PullRequest
61 голосов
/ 23 августа 2010

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

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

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

Ответы [ 6 ]

54 голосов
/ 23 августа 2010

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

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

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

17 голосов
/ 23 августа 2010

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

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

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

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

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

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

14 голосов
/ 23 августа 2010

Для чего бы то ни было, на самом деле здесь есть два вида «отладки»:

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

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

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

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

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

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

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

2 голосов
/ 23 августа 2010

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

0 голосов
/ 23 октября 2010

Если вы можете подождать, пока программа не закончит работу, прежде чем изучать вывод, то укладка монады Writer является классическим подходом к реализации регистратора. Я использую это здесь для возврата набора результатов из нечистого кода HDBC.

0 голосов
/ 23 августа 2010

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

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

...