Становится ли монада IO строгой при использовании паттерна взрыва? - PullRequest
2 голосов
/ 25 октября 2019

Я ожидаю следующий фрагмент кода:

main = do
    let !x = [2,3,5,2,3,5,6,7,1,3,0,1]
    begin <- getCPUTime
    let !rx = reverse x
    end <- getCPUTime
    putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
    putStrLn $ "Result: " ++ show rx

идентичен следующей версии:

main = do
    let x = [2,3,5,2,3,5,6,7,1,3,0,1]
    begin <- x `seq` getCPUTime
    let rx = reverse x
    end <- rx `seq` getCPUTime
    putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
    putStrLn $ "Result: " ++ show rx

Это правда? Это неверно, если x и rx оценивает WHNF, когда «необходимо», в первой версии.

Кстати, я хочу предложить синтаксический сахар для глубокой оценки, называемый «паттерн двойного взрыва».

1 Ответ

2 голосов
/ 26 октября 2019

Определенные вами примеры кода генерируют идентичный скомпилированный код. Если вы возьмете следующую программу:

{-# LANGUAGE BangPatterns #-}

module Bang where

import System.CPUTime

main1 = do
    let !x = [2,3,5,2,3,5,6,7,1,3,0,1]
    begin <- getCPUTime
    let !rx = reverse x
    end <- getCPUTime
    putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
    putStrLn $ "Result: " ++ show rx

main2 = do
    let x = [2,3,5,2,3,5,6,7,1,3,0,1]
    begin <- x `seq` getCPUTime
    let rx = reverse x
    end <- rx `seq` getCPUTime
    putStrLn $ "Calculation time: " ++ show (end - begin) ++ " ps."
    putStrLn $ "Result: " ++ show rx

и скомпилируете ее (с версией GHC 8.6.5):

stack ghc -- -dsuppress-all -dsuppress-uniques -ddump-simpl -fforce-recomp -O2 Bang.hs

вы обнаружите в выгруженном ядре GHC, что main1и main2 скомпилированы в точно такой же код, который фактически извлекается в отдельную функцию main4:

main1
main1 = main4 `cast` <Co:3>

main2
main2 = main4 `cast` <Co:3>

HOWEVER , в общем случае, конструкция let !x = ...не совсем эквивалентно использованию let x = ..., за которым следует x `seq` y. Например, следующие два действия ввода-вывода различны:

foo :: IO ()
foo = do
  let !x = undefined
  return ()

bar :: IO ()
bar = do
  let x = undefined
  return $ x `seq` ()

Первое немедленно генерирует исключение:

main = do
    print 1
    foo       -- EXCEPTION!
    print 2

Второе ничего не делает при выполнении, но генерирует исключение, если вы попытаетесьчтобы проанализировать его результат:

main = do
    print 1
    bar        -- does nothing
    print 2
    x <- bar   -- also does nothing
    print 3
    () <- bar  -- EXCEPTION!
    print 4

Я полагаю, что после десугаринга bar и foo эквивалентны:

bar = return (undefined `seq` ())
foo = undefined `seq` return ()

, что объясняет их различное поведение.

...