Наблюдение за тем, как часто значение оценивалось в Haskell - PullRequest
1 голос
/ 05 августа 2020

С небольшим количеством unsafe вы можете увидеть, как большая ленивого значения была оценена в Haskell

import Data.IORef
import System.IO.Unsafe

data Nat = Z | S Nat
  deriving (Eq, Show, Read, Ord)

natTester :: IORef Nat -> Nat
natTester ref =
  let inf = natTester ref
   in unsafePerformIO $ do
        modifyIORef ref S
        pure $ S inf

newNatTester :: IO (Nat, IORef Nat)
newNatTester = do
  ref <- newIORef Z
  pure (natTester ref, ref)

howMuchWasEvaled :: (Nat -> b) -> IO Nat
howMuchWasEvaled f = do
  (inf, infRef) <- newNatTester
  f inf `seq` readIORef infRef

С:

ghci> howMuchWasEvaled $ \x -> x > S (S Z)
S (S (S Z))

Это означает, что были оценены только первые четыре конструктора infinity :: Nat.

Если x используется дважды, мы все равно получим общую оценку, которая была необходима:

> howMuchWasEvaled $ \x -> x > Z && x > S (S Z)
S (S (S Z))

В этом есть смысл - как только мы оценили x до определенного момента, нам не нужно начинать все заново. Преобразователь уже был принудительно выполнен.

Но - это есть способ проверить, сколько раз конструкторов было оценено? То есть функция magic, которая ведет себя следующим образом:

> magic $ \x -> x > Z 
S Z
> magic $ \x -> x > Z && x > Z
S (S Z)
...

Я понимаю, что это может включать флаги компилятора (возможно, no-cse), встроенные прагмы, очень небезопасные функции и т. Д. c.

РЕДАКТИРОВАТЬ : Карл указал, что я, возможно, недостаточно ясно понял ограничения того, что я искал. Требование состоит в том, чтобы функция , которая magic задана как аргумент, не могла быть изменена (хотя можно предположить, что ее аргумент является ленивым). magic будет частью библиотеки, которую вы можете вызывать с помощью своих собственных функций.

Тем не менее, GH C -specifi c хаки и вещи, которые работают только ненадежно, определенно остаются в игре.

1 Ответ

3 голосов
/ 05 августа 2020

Как указано, это невозможно сделать в gh c. Два использования одного и того же имени, например x в вашем примере, всегда будут использоваться совместно с реализацией модели оценки haskell в gh c. Это гарантия, которая является ключевым строительным блоком для обеспечения того, чтобы обмен был . По крайней мере, чтобы заставить это делать то, что вы хотите, потребуется передать несколько значений, по одному для каждого независимого места, где вы хотите использовать указанное значение.

Тогда вам нужно будет убедиться, что на вызывающей стороне , значения не передаются в функцию случайно. Это можно сделать, но это место, где могут потребоваться такие параметры, как -fno-cse или -fno-full-laziness, в зависимости от того, как вы это реализуете и на каком уровне оптимизации работает gh c.

Вот небольшая модификация вашей начальной точки, которая работает в ghci, по крайней мере:

{-# OPTIONS_GHC -fno-full-laziness #-}

import Data.IORef
import System.IO.Unsafe

data Nat = Z | S Nat
    deriving (Eq, Show, Read, Ord)

natTester :: IORef Nat -> Nat
natTester ref =
    let inf = natTester ref
    in unsafePerformIO $ do
        modifyIORef ref S
        pure $ S inf

newNatTester :: IO ((a -> Nat), IORef Nat)
newNatTester = do
    ref <- newIORef Z
    pure (\x -> x `seq` natTester ref, ref)

howMuchWasEvaled :: ((a -> Nat) -> b) -> IO Nat
howMuchWasEvaled f = do
    (infGen, infRef) <- newNatTester
    f infGen `seq` readIORef infRef

Использование в ghci:

*Main> howMuchWasEvaled $ \gen -> let x = gen 1 ; y = gen 2 in x > Z && y > Z
S (S Z)
*Main> howMuchWasEvaled $ \gen -> let x = gen 1 in x > Z && x > Z
S Z

Я заменил передачу одной бесконечности в функцию передачей ей генератор бесконечности. Генератору безразлично, с каким аргументом он вызывается, если это не нижнее значение. (seq нужен, чтобы убедиться, что функция действительно использует свой аргумент, чтобы предотвратить ряд оптимизаций gh c может go, если аргумент не использовался.) Пока он вызывается с другим значением каждый раз , gh c не сможет удалить его, потому что выражения разные. При использовании с оптимизацией полная лень может помешать, выплыв natTester ref из лямбды в newNatTester. Чтобы предотвратить это, я добавил прагму, чтобы отключить эту оптимизацию в этом модуле. По умолчанию это не имеет значения в ghci, так как он не использует оптимизацию. Это может иметь значение, если этот модуль будет скомпилирован, поэтому я добавил прагму на всякий случай.

...