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

Лучшей, которую я видел до сих пор, является Креветка:

26
задан Brian 20 August 2009 в 11:17
поделиться

3 ответа

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

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

Маленький глупый пример. У вас есть программа на 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 Debugger в руководстве GHC. Также имеется Leksah IDE , которую я еще не использовал, поскольку я являюсь пользователем VIM и терминала, но у нее есть графический интерфейс для отладчика GHCi, согласно руководству.

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

foo :: Int -> Int

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

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

trace :: String -> a -> a

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

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

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

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

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

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

30
ответ дан 28 November 2019 в 07:27
поделиться

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

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

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

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

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

2
ответ дан 28 November 2019 в 07:27
поделиться
Другие вопросы по тегам:

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