Я собираюсь продемонстрировать проблему, используя следующую примерную программу
{-# LANGUAGE BangPatterns #-}
data Point = Point !Double !Double
fmod :: Double -> Double -> Double
fmod a b | a < 0     = b - fmod (abs a) b 
         | otherwise = if a < b then a 
                       else let q = a / b 
                            in b * (q - fromIntegral (floor q :: Int))
standardMap :: Double -> Point -> Point
standardMap k (Point q p) = 
   Point (fmod (q + p) (2 * pi)) (fmod (p + k * sin(q)) (2 * pi))
iterate' gen !p = p : (iterate' gen $ gen p)
main = putStrLn 
     . show 
     . (\(Point a b) -> a + b) 
     . head . drop 100000000 
     . iterate' (standardMap k) $ (Point 0.15 0.25)
    where k = (cos (pi/3)) - (sin (pi/3))
Здесь standardMap k - параметризованная функция, а k=(cos (pi/3))-(sin (pi/3)) - параметр. Если я скомпилирую эту программу с помощью ghc -O3 -fllvm, время выполнения на моей машине примерно равно 42s, однако, если я пишу k в форме 0.5 - (sin (pi/3)), время выполнения равно 21s, и если я напишу k = 0.5 - 0.5 * (sqrt 3), будет принимать только 12s.
Вывод состоит в том, что k переоценивается при каждом вызове standardMap k.
Почему это не оптимизировано?
P.S. компилятор ghc 7.6.3 на archlinux
ИЗМЕНИТЬ
Для тех, кто связан со странными свойствами standardMap, здесь представлен более простой и интуитивно понятный пример, который показывает ту же проблему
{-# LANGUAGE BangPatterns #-}
data Point = Point !Double !Double
rotate :: Double -> Point -> Point
rotate k (Point q p) = 
   Point ((cos k) * q - (sin k) * p) ((sin k) * q + (cos k) * p)
iterate' gen !p = p : (iterate' gen $ gen p)
main = putStrLn 
     . show 
     . (\(Point a b) -> a + b) 
     . head . drop 100000000 
     . iterate' (rotate k) $ (Point 0.15 0.25)
   where --k = (cos (pi/3)) - (sin (pi/3))
         k = 0.5 - 0.5 * (sqrt 3)
EDIT
Прежде чем я задал вопрос, я попытался сделать k strict, так же, как предложил Дон, но с ghc -O3 я не видел разницы. Решение со строгостью работает, если программа скомпилирована с помощью ghc -O2. Я пропустил это, потому что я не пытался использовать все возможные комбинации флагов со всеми возможными версиями программы.
В чем разница между -O3 и -O2, которая влияет на такие случаи?
Должен ли я предпочитать -O2 вообще?
EDIT
Как заметил Майк Хартл и другие, если rotate k изменено на rotate $ k или standardMap k на standardMap $ k, производительность улучшится, хотя это не самое лучшее (решение Don). Почему?