С расширений языка, мы можем создавать ситуации, в которых f x
необходимо быть оценены несколько раз:
{-# LANGUAGE GADTs, Rank2Types #-}
module MultiEvG where
data BI where
B :: (Bounded b, Integral b) => b -> BI
foo :: [BI] -> [Integer]
foo xs = let f :: (Integral c, Bounded c) => c -> c
f x = maxBound - x
g :: (forall a. (Integral a, Bounded a) => a) -> BI -> Integer
g m (B y) = toInteger (m + y)
x :: (Integral i) => i
x = 3
in map (g (f x)) xs
Гвоздь должен иметь f x
полиморфный даже в качестве аргумента g
, и мы должны создать ситуацию, в которой тип (ы), в котором это необходимо, не может быть предсказано (мой первый удар использовал Either a b
вместо BI
, но при оптимизации это, разумеется, привело только к двум оценкам f x
).
Полиморфное выражение должно оцениваться по крайней мере один раз для каждого типа, в котором он используется. Это одна из причин ограничения мономорфизма. Однако, когда диапазон типов, в котором он может понадобиться, ограничен, возможно запоминать значения для каждого типа, и в некоторых случаях GHC делает это (требуется оптимизация, и я ожидаю, что количество типов не должно быть слишком большой). Здесь мы сталкиваемся с тем, что является в основном неоднородным списком, поэтому при каждом вызове g (f x)
он может понадобиться произвольному типу, удовлетворяющему ограничениям, поэтому вычисление невозможно снять за пределами map
(технически компилятор все еще может создавать кеш значений для каждого используемого типа, поэтому он будет оцениваться только один раз для каждого типа, но GHC не имеет, по всей вероятности, это не стоило бы проблем).
- Мономорфные выражения нужно оценивать только один раз, их можно обменивать. Независимо от того, выполняются ли они до реализации; по чистоте, это не изменяет семантику программы. Если выражение привязано к имени, на практике вы можете полагаться на его совместное использование, поскольку это легко и очевидно, что хочет программист. Если это не связано с именем, это вопрос оптимизации. С генератором байткода или без оптимизаций выражение часто будет оцениваться повторно, но при повторной оценке оптимизации будет указываться ошибка компилятора.
- Полиморфные выражения должны оцениваться по крайней мере один раз для каждого типа, в котором они используются, но с оптимизацией, когда GHC может видеть, что он может использоваться несколько раз в одном типе, он будет (обычно) по-прежнему использоваться для этого типа при более широком вычислении.
Нижняя строка: всегда скомпилируйте с оптимизацией, помогите компилятору путем привязки выражений, которые вы хотите использовать для имени, и дайте мономорфные сигнатуры типов, где это возможно.
У вас есть пример, где 'е x' является оцениваемым более чем один раз? – hammar
@hammar: Я добавил такой пример. –
@Grzegorz Вы должны упомянуть, что это выполняется только в том случае, если вы не оптимизируете. Если вы допускаете более высокие типы рангов, я могу привести пример, когда оптимизация не может устранить повторную оценку. Заинтересовались? –