Если у меня есть функция fun
типа:
-- For example, `SomeRecord` can have more than 2 fields,
-- but 20 or 30
data SomeRecord a b = SomeRecord {
f1 :: a -> b,
f2 :: a -> b -> Bool
}
fun :: SomeRecord Int Bool
fun = SomeRecord ...
и затем я вызываю fun
в разных местах моей программы, будет ли код, созданный GHC, вызывать fun
каждый раз или он будет заменен некоторой «ссылкой» на результат этой нулевой (фактически постоянной) функции, как компиляторы C делают это с константами? Что-то вроде «вызовите его в первый раз, а затем замените этим значением везде, на любой глубине вложенности» (или GHC мог бы сделать это даже без вызова), возможно ли это? Нужны ли мне для этого какие-то специальные флаги компилятора (сейчас я использую только -O2
)?
fun
вообще не является функцией. Это просто значение типа SomeRecord Int Bool
. Думаю, из-за лени можно было бы спросить, «вызывается» ли SomeRecord
несколько раз, один раз за использование fun
.
Я понимаю лень, я не понимаю, где сохраняются эти thunks/values: какие-то вложенные контексты? Глобальное значение - может быть, кэшировано в глобальном пространстве имен... а как насчет модулей? ChatGPT говорит, что в GHC существует «совместное использование» и он разделяет значения, но из-за прозрачности ссылок я ожидаю, что даже локальные/вложенные «константы» будут «совместно использоваться» (оцениваются один раз), но я не уверен.
@RandomB В первом приближении, если вы определяете имя в некоторой области, то каждый раз, когда вы ссылаетесь на одно и то же имя в той же области, это просто ссылка на одно общее значение. Таким образом, если вы определяете имя в глобальной области (с определением верхнего уровня, например fun
), тогда существует одно значение, которое используется всеми ссылками (и импорт определений верхнего уровня из других модулей считается одной и той же глобальной областью времени выполнения для эту цель). Это не является жесткой гарантией, поскольку компилятор может «отделить» ресурсы для оптимизации, но оптимизация предназначена для этого; он старается не усугублять ситуацию.
В GHC fun
скомпилируется в объект, который при первом вычислении перезаписывает себя вычисленным значением. Это произойдет даже без каких-либо оптимизаций. (Действительно, отключить это поведение — трудная задача, а не включить его.)
В GHC
fun
будет оцениваться только один раз, аf1 fun someInt
будет оцениваться один раз за вызов. Я не уверен в том, чего вы здесь ожидаете. Кроме того, вы можете поэкспериментировать сDebug.Trace.trace
, чтобы увидеть, что и когда оценивается (даже если это может измениться в зависимости от оптимизации).