GHC.Stack.HasCallStack — это лёгкий способ получить частичный стек вызовов в любой точке программы.
Функция может запросить место своего вызова с помощью ограничения HasCallStack и получить его в виде значения Haskell, используя callStack.
Затем можно использовать функции из GHC.Stack для просмотра стека вызовов или его красивой печати (как показано в f ниже).
f :: HasCallStack => IO () f = putStrLn (prettyCallStack callStack) g :: HasCallStack => IO () g = f
Вычисление f напрямую показывает стек вызовов с одной записью, тогда как вычисление g, которое также запрашивает место своего вызова, показывает две записи — по одной для каждого вычисления, «аннотированного» с помощью HasCallStack.
ghci> f CallStack (from HasCallStack): f, called at <interactive>:19:1 in interactive:Ghci1 ghci> g CallStack (from HasCallStack): f, called at <interactive>:17:5 in main:Main g, called at <interactive>:20:1 in interactive:Ghci2
Функция error из Prelude поддерживает вывод стека вызовов, приведших к ошибке, вместе с обычным сообщением об ошибке:
ghci> error "bad" *** Exception: bad CallStack (from HasCallStack): error, called at <interactive>:25:1 in interactive:Ghci5
Здесь стек вызовов состоит из одной записи, указывающей на место вызова error. Однако аннотирование нескольких вычислений с помощью HasCallStack значительно упрощает выяснение точных обстоятельств и последовательностей вызовов, приводящих к вызову error, как показано в простом примере ниже.
f :: HasCallStack => IO () f = error "bad bad bad" g :: HasCallStack => IO () g = f h :: HasCallStack => IO () h = g
ghci> h *** Exception: bad bad bad CallStack (from HasCallStack): error, called at call-stack.hs:4:5 in main:Main f, called at call-stack.hs:7:5 in main:Main g, called at call-stack.hs:10:5 in main:Main h, called at <interactive>:28:1 in interactive:Ghci1
CallStack расширяется только настолько, насколько это позволяют типы, например:
myHead :: HasCallStack => [a] -> a myHead [] = error "empty" myHead (x:xs) = x bad :: Int bad = myHead []
ghci> bad *** Exception: empty CallStack (from HasCallStack): error, called at Bad.hs:8:15 in main:Bad myHead, called at Bad.hs:12:7 in main:Bad
содержит место вызова error в myHead и myHead в bad, но не место вызова bad в приглашении GHCi.
GHC разрешает ограничения HasCallStack в два этапа:
- Если в области видимости есть
CallStack— то есть если для охватывающего определения задано ограничениеHasCallStack, — GHC добавит новое место вызова в существующийCallStack. - В противном случае GHC разрешит ограничение
HasCallStackдля синглтонаCallStack, содержащего только текущее место вызова.
Важно: GHC никогда не выводит ограничение HasCallStack автоматически — его необходимо запрашивать явно.
CallStack остаётся абстрактным типом, но GHC предоставляет функцию
getCallStack :: CallStack -> [(String, SrcLoc)]
для доступа к отдельным местам вызова в стеке. String — это имя вызванной функции, а SrcLoc содержит название пакета, модуля и файла, а также номера строки и столбца.
GHC.Stack также экспортирует функцию withFrozenCallStack, позволяющую пользователям зафиксировать текущий CallStack и предотвратить влияние любых последующих операций добавления. Авторы библиотек могут использовать её, чтобы не допустить раскрытия CallStack ненужных подробностей реализации. Рассмотрим приведённый выше пример с myHead: строка error в выведенном стеке не особенно информативна, поэтому можно скрыть её, зафиксировав CallStack, передаваемый в error.
myHead :: HasCallStack => [a] -> a myHead [] = withFrozenCallStack (error "empty") myHead (x:xs) = x
ghci> myHead [] *** Exception: empty CallStack (from HasCallStack): myHead, called at Bad.hs:12:7 in main:Bad
ПРИМЕЧАНИЕ: Внимательный пользователь может заметить, что HasCallStack — это просто псевдоним неявного параметра ?callStack :: CallStack. Это деталь реализации, и её не следует считать частью API CallStack; в будущем мы можем изменить реализацию.
6.19.6.1. Сравнение с другими источниками трассировок стека
HasCallStack не взаимодействует с RTS и не требует компиляции с -prof. С другой стороны, поскольку CallStack явно формируется с помощью ограничений HasCallStack, обычно он содержит меньше информации, чем моделируемые стеки вызовов, которые поддерживает RTS.