из сообщества Ocaml, я пытаюсь немного изучить Haskell. Переход проходит довольно хорошо, но я немного запутался с отладкой. Я использовал (много) «printf» в своем коде ocaml, чтобы проверить некоторые промежуточные значения, или в качестве флага, чтобы увидеть, где вычисление точно провалилось.
Поскольку printf является действием IO , я должен поднять весь мой код haskell внутри монады IO , чтобы иметь возможность такого рода отладки? Или есть лучший способ сделать это (я действительно не хочу делать это вручную, если этого можно избежать)
Я также нахожу функцию trace : http://www.haskell.org/haskellwiki/Debugging#Printf_and_friends что кажется именно тем, что я хочу, но я не понимаю его тип: нигде нет IO ! Может кто-нибудь объяснить мне, как работает функция трассировки?
trace
- самый простой в использовании метод отладки. Это не в IO
точно по той причине, которую вы указали: нет необходимости поднимать ваш код в монаде IO
. Это реализовано так
trace :: String -> a -> a
trace string expr = unsafePerformIO $ do
putTraceMsg string
return expr
Итак, за кулисами есть ввод-вывод, но для выхода из него используется unsafePerformIO
. Это функция, которая потенциально нарушает ссылочную прозрачность, о чем вы можете догадаться, глядя на ее тип IO a -> a
, а также ее имя.
trace
просто сделана нечистой. Смысл монады IO
в том, чтобы сохранить чистоту (нет IO, незамеченного системой типов) и определить порядок выполнения утверждений, которые иначе были бы практически не определены через ленивую оценку.
Однако, на свой страх и риск, вы можете скомпоновать IO a -> a
, т.е. выполнить нечистый IO. Это хак и, конечно, "страдает" от ленивой оценки, но это то, что делает trace просто для отладки.
Тем не менее, для отладки, вероятно, следует идти другими путями:
Снижение необходимости отладки промежуточных значений
Используйте точки останова и т.д. (отладка на основе компилятора)
Используйте общие монады. Если ваш код все же монадический, пишите его независимо от конкретной монады. Используйте тип M a = ...
вместо обычного IO ...
. После этого вы можете легко объединить монады с помощью трансформаторов и наложить на них отладочную монаду. Даже если необходимость в монадах отпадет, можно просто вставить Identity a
для чистых значений.
Ну, поскольку весь Haskell построен на принципе ленивой оценки (так что порядок вычислений фактически недетерминирован), использование printf'ов имеет в нем очень мало смысла.
Если REPL+инспекция результирующих значений действительно недостаточны для отладки, то обернуть все в IO - единственный выбор (но это не ПРАВИЛЬНЫЙ путь программирования на Haskell).
Как бы то ни было, здесь есть два вида «отладки»:
В строгом императивном языке они обычно совпадают. В Haskell они часто не делают этого:
Если вы просто хотите вести журнал промежуточных значений, есть много способов сделать это - например, вместо того, чтобы переносить все в IO
, простую монаду Writer
будет достаточно, это эквивалентно тому, что функции возвращают кортеж из двух фактических результатов и значение аккумулятора (как правило, своего рода список).
Также обычно не требуется помещать все в монаду, только функции, которые должны записывать в значение «log» - например, вы можете вынести только те части выражения, которые могут потребоваться выполните регистрацию, оставив основную логику чистой, затем заново соберите все вычисления, объединив чистые функции и записывая вычисления обычным способом с помощью fmap
и т. д. Имейте в виду, что Writer
- это своего рода извинение для монады: без возможности читать из журнала, только запись в него, каждое вычисление логически не зависит от его контекста, что упрощает манипулирование вещами.
Но в некоторых случаях даже это излишество - для многих чистых функций достаточно просто перенести подвыражения на верхний уровень и попробовать что-то в REPL.
Однако, если вы действительно хотите проверить поведение чистого кода во время выполнения - например, чтобы выяснить, почему подвыражение расходится - в целом нет способа сделать это из другого чистого кода - по сути, это определение чистоты. Так что в этом случае у вас нет другого выбора, кроме как использовать инструменты, которые существуют «вне» чистого языка: либо нечистые функции, такие как unsafePerformPrintfDebugging
- эррр, я имею в виду trace
- либо модифицированная среда выполнения, такая как отладчик GHCi.
trace
также имеет тенденцию переоценивать свои аргументы в пользу печати, теряя при этом многие преимущества лени.