Функциональное программирование. Конспект лекций
.pdf
для просмотра значения переменной var.
Для продолжения выполнения программы вводится команда
:continue.
Программа будет выполняться до следующей точки останова.
9.3. Трассировка
При отладке программы часто интересно знать последовательность выполнения команд и вызовов процедур и функций. Императивные отладчики обычно предоставляют функцию отслеживания стека, которая позволяет видеть стек активных вызовов функций, описывающих путь к текущему местоположению. К сожалению, это трудно обеспечить в Haskell, потому что выполнение происходит на основе требований и необходимости вычисления выражений. Тем не менее в Haskell предоставляется способ возврата от точки прерывания к предыдущим этапам оценки, т.е. это переход на один шаг назад для получения информации о ходе вызовов функций.
Для трассировки и дальнейшего анализа используются коман-
ды:
:trace
:history
:back :forward.
Следующий пример показывает использование трассировки для функции быстрой сортировки qsort:
*Main> :list qsort
1qsort [] = []
2qsort (a:as) = qsort left ++ [a] ++ qsort right
3where (left,right) = (filter (<=a) as, filter (>a) as)
*Main> :b 1
Breakpoint 1 activated at qsort.hs:1:11-12 *Main>
–101 –
Точка прерывания установлена в строке 1, т.е. qsort [] = []. Запуск трассировки:
*Main> :trace qsort [3,2,1] Stopped at qsort.hs:1:11-12 _result :: [a] [qsort.hs:1:11-12] *Main>
Просмотр результатов трассировки:
[qsort.hs:1:11-12] *Main> :history -1 : qsort.hs:3:24-38
-2 : qsort.hs:3:23-55
-3 : qsort.hs:(1,0)-(3,55) -4 : qsort.hs:2:15-24
-5 : qsort.hs:2:15-46 -6 : qsort.hs:3:24-38 -7 : qsort.hs:3:23-55
-8 : qsort.hs:(1,0)-(3,55) -9 : qsort.hs:2:15-24
-10 : qsort.hs:2:15-46 -11 : qsort.hs:3:24-38 -12 : qsort.hs:3:23-55
-13 : qsort.hs:(1,0)-(3,55) -14 : qsort.hs:2:15-24
-15 : qsort.hs:2:15-46
-16 : qsort.hs:(1,0)-(3,55) <end of history>
Просмотр переменных и выражений на каждом шаге выполня-
ется с помощью команд :back, :forward:, :print var, :force var.
[qsort.hs:1:11-12] *Main> :back Logged breakpoint at qsort.hs:3:24-38 _result :: [a]
as :: [a]
– 102 –
a :: a
[-1: qsort.hs:3:24-38] *Main> *Main> :print a
a = 2
Команда :trace может использоваться без указания выражения. В этом случае трассировка начинается с текущей точки останова. Команда :history доступна только при использовании :trace. По умолчанию отладчик запоминает последние 50 шагов в истории, но это можно изменить с помощью команды:
:set -fghci-hist-size = n.
9.4. Исключения
Исключения могут возникнуть при ошибках, и при этом отсутствует контекстная информация. Поиск того, какой именно вызов в программе привел к ошибке, может быть кропотливым процессом, обычно с использованием Debug.Trace.trace или компиляцией с профилированием и использованием Debug.Trace.traceStack.
Отладчик Haskell предлагает способ пролить свет на эти ошибки быстро, без изменения или перекомпиляции исходного кода. Одним из способов было бы установить точку прерывания на месте в исходном коде, которое генерирует исключение, а затем использовать :trace и :history для установления контекста. Однако часто функция, вызвавшая исключение, находится в библиотеке, и на нее нельзя установить точку прерывания напрямую. По этой причине Haskell предоставляет флаги -fbreak-on-exception, которые вызывают остановку оценщика при возникновении исключения, и -fbreak- on-error, который работает аналогичным образом, но останавливается только на неперехваченных исключениях. При остановке на исключении отладчик будет действовать так же, как и при достижении точки прерывания, с тем отличием, что не будет показан исходный код, например:
*Main> :set -fbreak-on-exception
*Main> :trace qsort ("abc" ++ undefined) “Stopped at <exception thrown>
– 103 –
_exception :: e
[<exception thrown>] *Main> :history -1 : qsort.hs:3:24-38
-2 : qsort.hs:3:23-55
-3 : qsort.hs:(1,0)-(3,55) -4 : qsort.hs:2:15-24
-5 : qsort.hs:2:15-46
-6 : qsort.hs:(1,0)-(3,55) <end of history>
[<exception thrown>] *Main> :back Logged breakpoint at qsort.hs:3:24-38 _result :: [a]
as :: [a] a :: a
[-1: qsort.hs:3:24-38] *Main> :force as
*** Exception: Prelude.undefined
[-1: qsort.hs:3:24-38] *Main> :print as as = 'b' : 'c' : (_t1::[Char])
В приведенном примере после возникновения исключения выдается команда :history, и с помощью команд :back и :force проводится анализ переменных.
9.5. Пример отладки
Рассмотрим пример отладки функции быстрой сортировки списка. Алгоритм быстрой сортировки заключается в том, что выбирается опорный элемент и список делится на два. В левом списке элементы меньше или равны опорному, а в правом – больше опорного. Далее процесс рекурсивно повторяется для левого и правого списков. Ниже представлена функция быстрой сортировки списков:
module TestTrace where
qsort [] = []
qsort (a:as) = qsort left ++ [a] ++ qsort right
– 104 –
where (left,right) = (filter (<=a) as, filter (>a) as)
9.5.1. Пошаговая отладка
Процесс пошаговой отладки можно представить в виде следующих шагов.
1.Установить параметры отладки:
:set stop :list
:set -fprint-evld-with-show.
2.Выдать команду :list qsort и посмотреть номера строк функ-
ции qsort:
*TestTrace> :list qsort 2
3qsort [] = []
4qsort (a:as) = qsort left ++ [a] ++ qsort right
5where (left,right) = (filter (<=a) as, filter (>a) as)
3. Задать точки останова и проверить корректность их установ-
ки:
TestTrace> :break 4
Breakpoint 0 activated at testTrace.hs:4:16-47 *TestTrace> :show breaks
[0] TestTrace testTrace.hs:4:16-47
4. Запустить программу на выполнение, введя имя функции и ее аргументы, например qsort [5,8,1,4,2]. Выполнение остановится в точке прерывания и будет выдана информация о точке останова, значениях связанных переменных в виде
[testTrace.hs:4:16-47] *TestTrace> :step Stopped at testTrace.hs:4:16-25
– 105 –
_result :: [t] = _ left :: [t] = _
3qsort [] = []
4qsort (a:as) = qsort left ++ [a] ++ qsort right
^^^^^^^^^^
5 where (left,right) = (filter (<=a) as, filter (>a) as)
Здесь символами «^» указывается место прерывания функции. 5. Ввести команды :print <имя переменной> или :force <имя переменной> для того, чтобы проанализировать значения перемен-
ных.
[testTrace.hs:5:42-55] *TestTrace> :print as as = [4,2]
6. Ввести команду :step (или :continue) и перейти к следующей точке прерывания. При рекурсивном вызове функций будет происходить многократное перемещение от одних выражений (блоков выполнения) к другим, до тех пор пока функция не завершить работу.
[testTrace.hs:(3,1)-(5,56)] *TestTrace> :step Stopped at testTrace.hs:5:42-55
_result :: [Integer] = _ a :: Integer = 1
as :: [Integer] = [4,2]
4qsort (a:as) = qsort left ++ [a] ++ qsort right
5where (left,right) = (filter (<=a) as, filter
(>a) as)
^^^^^^^^^^^^^^
9.5.2. Трассировка
Для выполнения трассировки целесообразно добавить в исходную программу функцию trace или traceShow:
– 106 –
module TestTrace where
import Debug.Trace
qsort [] = []
qsort (a:as) = trace ("left:" ++ show left) (qsort left) ++ [a] ++ trace ("right:" ++ show right) (qsort right)
where (left,right) = (filter (<=a) as, filter (>a) as)
Функция trace находится в модуле Debug.Trace имеет тип trace :: String -> a -> a.
С ее помощью в трассировку можно добавлять отладочную информацию. В данном примере при каждом вызове qsort будет до-
бавляться строка "left:" ++ show left или "right:" ++ show right, т.е.
содержимое левого или правого списка. Это позволит проанализировать, каким образом происходит разбиение исходного списка на левый и правый и как это происходит рекурсивно.
При трассировке надо выполнить шаги с 1 по 3, которые описаны выше для пошаговой отладки. Далее выполняем следующие шаги.
4. Запустить трассировку командой :trace qsort [5,8,1,4,2]:
*TestTrace> :trace qsort [5,8,1,4,2] left:[1,4,2]
left:[]
Stopped at testTrace.hs:5:12-13 _result :: [a] = _
4
5 qsort [] = []
^^
6 qsort (a:as) = trace ("left:" ++ show left) (qsort left) ++ [a] ++ trace ("right:" ++ show right) (qsort right)
– 107 –
Трассировка продолжается до точки прерывания, установленной в строке 5. Точка прерывания выделена символами «^». Функция trace вывела информацию
left:[1,4,2],
что говорит о том, что в левый список попали элементы меньшие 5. 5. Выполнить команду :history и посмотреть «маршрут» вызова
выражений до точки прерывания:
[testTrace.hs:5:12-13] *TestTrace> :history -1 : qsort (testTrace.hs:(5,1)-(7,56))
-2 : qsort (testTrace.hs:6:46-55)
-3 : qsort:(...) (testTrace.hs:7:25-39) -4 : qsort:(...) (testTrace.hs:7:24-56) -5 : qsort (testTrace.hs:6:34-42)
-6 : qsort (testTrace.hs:6:23-42) -7 : qsort (testTrace.hs:6:16-56) -8 : qsort (testTrace.hs:6:16-111)
-9 : qsort (testTrace.hs:(5,1)-(7,56)) -10 : qsort (testTrace.hs:6:46-55)
-11 : qsort:(...) (testTrace.hs:7:25-39) -12 : qsort:(...) (testTrace.hs:7:24-56) -13 : qsort (testTrace.hs:6:34-42)
-14 : qsort (testTrace.hs:6:23-42) -15 : qsort (testTrace.hs:6:16-56) -16 : qsort (testTrace.hs:6:16-111)
-17 : qsort (testTrace.hs:(5,1)-(7,56)) <end of history>
Из представленной информации видно, что до первого прерывания выполнено 17 шагов (начало вычислений – шаг № -17, далее -16 и последний шаг -1). Для перемещения по истории вычислений можно использовать команды :back и :forward и проводить анализ переменных с помощью :print и :force.
6. Выполнить команду :continue и продолжить анализ:
[testTrace.hs:5:12-13] *TestTrace> :continue right:[8]
– 108 –
left:[]
,5Stopped at testTrace.hs:5:12-13 _result :: [a] = _
4
5 qsort [] = []
^^
6 qsort (a:as) = trace ("left:" ++ show left) (qsort left) ++ [a] ++ trace ("right:" ++ show right) (qsort right)
Таким образом, можно анализировать последовательность вычисления выражений и оценивать значения переменных.
– 109 –
Список литературы
[1]Филд А., Харрисон П. Функциональное программирова-
ние. М.: Мир, 1993.
[2]Холомьев А. Учебник по Haskell // Книга зарегистрирована под лицензией Creative Commons Attribution-NonCommercial- NoDerivs 3.0 Generic license (CC BY-NC-ND 3.0), 2012.
[3]Роганова Н.А. Функциональное программирование: Учебное пособие. М.: ГИНФО, 2002.
[4]Официальный сайт языка Haskell. [Электронный ресурс.] –
Режим доступа: https://www.haskell.org (дата обращения: 15.04.2020).
[5]Душкин Р.В. 14 занимательных эссе о языке Haskell и функциональном программировании. М.: ДМК Пресс, 2011.
[6]Душкин Р.В. Справочник по языку Haskell. М.: ДМК Пресс,
2008.
– 110 –
