Как достигается отладка на ленивом функциональном языке программирования?

StackOverflow https://stackoverflow.com/questions/1303794

Вопрос

Я хотел бы знать, как достигается отладка на ленивом функциональном языке.
Можете ли вы использовать точки останова, инструкции печати и традиционные методы?Это вообще хорошая идея?
Насколько я понимаю, чистое функциональное программирование не допускает побочных эффектов, за исключением монад.
Порядок исполнения также не гарантируется.
Пришлось бы вам программировать монаду для каждого раздела кода, который вы хотите протестировать?Я хотел бы получить некоторое представление об этом вопросе от кого-то более опытного в этой области.

Это было полезно?

Решение

Ничто не мешает вам использовать точки останова в ленивой функциональной программе.Разница с нетерпеливой оценкой заключается в когда программа остановится на точке останова и на то, как будет выглядеть трассировка.Программа остановится, когда выражение, для которого установлена точка останова, действительно будет уменьшено (очевидно).

Вместо трассировки стека, к которой вы привыкли, вы получаете сокращения, которые привели к сокращению выражения с точкой останова в нем.

Маленький глупый пример.У вас есть эта программа Haskell.

add_two x = 2 + x

times_two x = 2 * x

foo = times_two (add_two 42)

И вы ставите точку останова в первой строке (add_two), затем оцените foo.Когда программа останавливается на точке останова, на понятном языке вы ожидали бы получить трассировку, подобную

add_two
foo

и times_two даже не начал оцениваться, но в отладчике GHCi вы получаете

-1  : foo (debug.hs:5:17-26)
-2  : times_two (debug.hs:3:14-18)
-3  : times_two (debug.hs:3:0-18)
-4  : foo (debug.hs:5:6-27)
<end of history>

это список сокращений, которые привели к сокращению выражения, на которое вы поставили точку останова.Обратите внимание, что это выглядит как times_two "вызванный" foo даже если это не делается явно.Из этого вы можете видеть, что оценка 2 * x в times_two (-2) действительно ли форсировала оценку (add_two 42) (-1) из foo линия.Оттуда вы можете выполнить шаг, как в императивном отладчике (выполните следующее сокращение).

Еще одно отличие от отладки на нетерпеливом языке заключается в том, что переменные могут быть еще не оцененными thunks.Например, на шаге -2 приведенного выше трассировки и проверки x, вы обнаружите, что это все еще недооцененный thunk (обозначенный скобками в GHCi).

Для получения более подробной информации и примеров (как пошагово выполнять трассировку, проверять значения, ...) смотрите раздел отладчика GHCi в руководстве по GHC.Есть также Лексах ИДЕ который я еще не использовал, поскольку я пользователь VIM и терминала, но у него есть графический интерфейс к отладчику GHCi в соответствии с руководством.

Вы также просили распечатать выписки.Только с чистыми функциями это не так легко возможно, поскольку оператор print должен был бы находиться внутри монады ввода-вывода.Итак, у вас есть чистая функция

foo :: Int -> Int

и если вы захотите добавить оператор трассировки, print вернет действие в монаде ввода-вывода, и поэтому вам придется настроить сигнатуру функции, в которую вы хотите поместить этот оператор трассировки, и сигнатуры функций, которые его вызывают...

Это не очень хорошая идея.Итак, вам нужен какой-то способ нарушить чистоту, чтобы получить инструкции трассировки.В Haskell это можно сделать с помощью unsafePerformIO.Там есть Debug.Trace модуль, у которого уже есть функция

trace :: String -> a -> a

который выводит строку и возвращает второй параметр.Было бы невозможно написать как чистую функцию (ну, если вы действительно собираетесь выводить строку, то есть).Он использует unsafePerformIO под капотом.Вы можете поместить это в чистую функцию для вывода отпечатка трассировки.

Пришлось бы вам программировать монаду для каждого раздела кода, который вы хотите протестировать?

Я бы предложил скорее обратное, сделать как можно больше функций чистыми (я предполагаю, что здесь вы имеете в виду монаду ввода-вывода для печати, монады не обязательно нечисты).Отложенная оценка позволяет вам очень четко отделить код ввода-вывода от кода обработки.

Являются ли императивные методы отладки хорошей идеей или нет, зависит от ситуации (как обычно).Я нахожу тестирование с помощью QuickCheck / SmallCheck гораздо более полезным, чем модульное тестирование на императивных языках, поэтому я бы сначала пошел этим путем, чтобы избежать как можно большей отладки.Свойства QuickCheck на самом деле создают приятные краткие спецификации функций (для меня большая часть тестового кода на императивных языках выглядит просто как еще один сгусток кода).

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

С другой стороны, отладка ! = тестирование, и если где-то что-то пойдет не так, вам могут помочь точки останова и трассировки.

Другие советы

Я не думаю, что эта тема может быть рассмотрена в течение короткого промежутка времени.Пожалуйста, ознакомьтесь с документами, доступными по следующим ссылкам:

  1. Теория отслеживания чисто функциональных программ.
  2. Публикации Haskell Tracer.
  3. Технологии отладки на Haskell.

Я никогда ни во что не вникал ужасно сложный в Haskell, но тот факт, что побочные эффекты практически исчезли, устранил большую часть необходимости в отладке.Чистые функции чрезвычайно просты в тестировании и проверке без использования отладчика.

С другой стороны, пару раз у меня был опыт, когда мне нужно было что-то отладить внутри монады, и в этом случае я уже мог печатать / регистрировать / что угодно.

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

Исходя из опыта работы с Клоджур (который ленив, функционален и поощряет, но не обеспечивает чистоту):

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

  • В ленивых функциональных языках, которые допускают побочные эффекты (включая Clojure), вы можете относительно легко вставлять printlns и другие журналы отладки.Лично я нахожу это очень полезным.Вы должны быть осторожны, когда они вызываются из-за лени, но если вы вообще не видите выходных данных, это может быть намеком на то, что ваш код не оценивается из-за лени.....

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

Позвольте мне рекламировать мой собственный инструмент для устранения проблем, связанных с ленью.Это помогло мне за час устранить утечку памяти, связанную с ленью, на отладку которой я уже потратил 2 дня.

http://www.haskell.org/pipermail/haskell-cafe/2012-January/098847.html

http://hackage.haskell.org/package/htrace

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top