Определение самого строгого приложения ($!) Не приводит к одинаковой производительности

Недавно я читаю это сообщение в блоге Майклом Снояном. В упражнении, предложенном там, я попытался определить $! оператор сам:

import Prelude hiding ( ($!) )

($!) :: (a -> b) -> a -> b
($!) f x = x 'seq' f x

mysum :: [Int] -> Int
mysum list0 =
  go list0 0
  where
    go [] total = total
    go (x:xs) total = go xs $! total + x

main = print $ mysum [1..1000000]

Я думал, что это работает хорошо, хотя использование памяти было ужасным. Мой первый вопрос: это. Почему это не помогло?

Затем я проверил его определение в Prelude. В нем говорится:

($!)                    :: (a -> b) -> a -> b
f $! x                  = let !vx = x in f vx  -- see #2273

Итак, я скопировал его в свой код:

{-# LANGUAGE BangPatterns #-}

import Prelude hiding ( ($!) )

($!) :: (a -> b) -> a -> b
($!) f x =
  let !vx = x
   in f vx

mysum :: [Int] -> Int
mysum list0 =
  go list0 0
  where
    go [] total = total
    go (x:xs) total = go xs $! total + x

main = print $ mysum [1..1000000]

и результат был следующим:

Linking mysum4 ...
500000500000
     209,344,064 bytes allocated in the heap
     130,602,696 bytes copied during GC
      54,339,936 bytes maximum residency (8 sample(s))
          66,624 bytes maximum slop
              80 MB total memory in use (0 MB lost due to fragmentation)

Вы можете видеть, насколько ужасно это сравнивается с результатом использования Prelude $! оператор:

Linking mysum4 ...
500000500000
     152,051,776 bytes allocated in the heap
          41,752 bytes copied during GC
          44,384 bytes maximum residency (2 sample(s))
          21,152 bytes maximum slop
               1 MB total memory in use (0 MB lost due to fragmentation)

Мой второй вопрос - откуда это произошло?

Более того, я думаю, что он может быть переписан следующим образом:

($!) :: (a -> b) -> a -> b
f $! !x = f x

Есть ли причина не делать этого? Это мой третий вопрос.

Ответ 1

Ага! Это вопрос приоритета. Вы забыли вставить строку:

infixr 0  $!

и поэтому, когда вы используете свою собственную версию, она анализируется как

go (x:xs) total = (go xs $! total) + x

который, очевидно, имеет ужасную производительность. Это почти совпадение, что он даже дает вам правильный ответ.