Строгая оценка целочисленного аккумулятора

Вот классическая первая попытка на заказ length функция:

length1    []  = 0
length1 (x:xs) = 1 + length1 xs

А вот хвосто-рекурсивная версия:

length2 = length2' 0 where
    length2' n    []  = n
    length2' n (x:xs) = length2' (n+1) xs

Тем не мение, (n+1) не будет оцениваться строго, но instad Haskell создаст thunk, верно?

Является ли это правильным способом предотвращения создания thunk, тем самым вынуждая строгую оценку (n+1)?

length3 = length3' 0 where
    length3' n    []  = n
    length3' n (x:xs) = length3' (n+1) $! xs

Как бы я достиг того же эффекта с seq вместо $!?

2 ответа

Решение

Обычный способ написать это будет так:

length3 :: [a] -> Int
length3 = go 0
    where
        go :: Int -> [a] -> Int
        go n []     = n
        go n (x:xs) = n `seq` go (n+1) xs

А именно, как сложить список в строгом аккумуляторе. GHC дает прямой перевод в Core:

Main.$wgo :: forall a_abz. GHC.Prim.Int# -> [a_abz] -> GHC.Prim.Int#
Main.$wgo =
  \ (n :: GHC.Prim.Int#) (xs :: [a_abz]) ->
    case xs of
      [] -> n
      _ : xs -> Main.$wgo a (GHC.Prim.+# n 1) xs

Показывает, что он не распакован (и, следовательно, строг) в аккумуляторе.

Я не думаю, что это правильно$! строг в своем втором аргументе, так что вы просто форсируете список, а не аккумулятор.

Вы можете получить правильную строгость, используя seq что-то вроде этого:

let n' = n + 1 in n' `seq` length3' n' xs

Я думаю, что более читаемая версия будет использовать шаблоны взрыва (расширение GHC):

length3' !n (x:xs) = ...
Другие вопросы по тегам