Ответ 1
Эффект добавления ограничения HasCallStack
к функции foo
более или менее эквивалентен следующему:
- добавление дополнительного входного аргумента для стека вызовов в список аргументов
foo
; - везде, где вызывается
foo
, создавая для него аргумент стека вызовов, помещая информационный фрейм (состоящий из имени функции "foo" и исходного местоположения, где он был вызван) в стек вызовов ввода (если вызываетсяfoo
из другой функции с ограничениемHasCallStack
) или в пустой стек вызовов (если он вызывается из функции без ограниченияHasCallStack
).
Итак... если у вас есть некоторые функции:
foo :: HasCallStack => Int -> String -> String
foo n = bar n '*'
bar :: HasCallStack => Int -> Char -> String -> String
bar n c str = if n >= 0 then c' ++ ' ':str ++ ' ':c'
else error "bad n"
where c' = replicate n c
baz :: String
baz = foo 3 "hello"
затем добавление HasCallStack
к foo
и bar
(но оставляя baz
в покое) в основном имеет тот же эффект, как если бы вы написали:
foo cs n = bar cs' n
where cs' = pushCallStack ("bar", <loc>) cs
bar cs n c str
= if n >= 0 then c' ++ ' ':str ++ ' ':c'
else error cs' "bad n"
where c' = replicate n c
cs' = pushCallStack ("error", <loc>) cs
baz = foo cs' 3 "hello"
where cs' = pushCallStack ("foo", <loc>) emptyCallStack
Таким образом, базовая, неоптимизированная стоимость производительности - это стоимость дополнительного параметра для каждой функции, украшенной HasCallStack
, плюс стоимость выделения thunk для предоставления этого параметра для каждой точки вызова украшенной функции. (Эти расходы оплачиваются, даже если ошибка не возникает.)
На практике оптимизированный код будет... эээ... оптимизирован. Например, если приведенный выше пример скомпилирован с -O2
, foo
будет встроен, а bar
будет специализироваться на определении baz
таким образом, что единственная стоимость выполнения стека вызовов состоит в том, что статический указатель (на блок для создания полного стека вызовов для вызова error
) передается в специализированную версию bar
(но игнорируется, поскольку не генерируется ошибка).
GHC, кажется, не достаточно умен, чтобы определить, что baz
никогда не последует примеру error
и поэтому вообще не нуждается в кадре стека.