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

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

Ответы [ 5 ]

29 голосов
/ 25 августа 2009

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

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

Маленький глупый пример. У вас есть эта программа на 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. Оттуда вы можете выполнить шаг, как в императивном отладчике (выполнить следующее сокращение).

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

Более подробную информацию и примеры (как пройти по трассировке, проверить значения, ...) см. В разделе отладчика GHCi в руководстве по GHC. Есть также Leksah IDE , который я еще не использовал, так как я являюсь пользователем VIM и терминалом, но в соответствии с инструкцией у него есть графический интерфейс для отладчика GHCi.

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

foo :: Int -> Int

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

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

trace :: String -> a -> a

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

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

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

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

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

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

6 голосов
/ 20 августа 2009

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

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

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

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

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

1 голос
/ 04 апреля 2011

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

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

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

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

0 голосов
/ 20 октября 2012

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

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

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

...