Проверьте, было ли значение оценено как слабая нормальная форма головы

В Haskell, возможно ли проверить, было ли значение оценено как слабая нормальная форма головы? Если функция уже существует, я ожидаю, что она будет иметь такую ​​подпись

evaluated :: a -> IO Bool

Есть несколько мест, где живет подобный функционал.

Предыдущий ответ познакомил меня с :sprint Команда ghci, которая будет печатать только часть значения, которая уже была вынуждена ослабить нормальную форму головы. :sprint может наблюдать, было ли значение оценено:

> let l = ['a'..]
> :sprint l
l = _
> head l
'a'
> :sprint l
l = 'a' : _

Это возможно в IO изучить свойства, которые в противном случае были бы запрещены. Например, можно сравнить в IO чтобы увидеть, пришли ли два значения из одной и той же декларации. Это обеспечивается StableName в System.Mem.StableName и используется лихо, чтобы решить наблюдаемую проблему совместного использования данных в reify. Связанный StablePtr не предоставляет механизм проверки, находится ли указанное значение в нормальной форме со слабой головой.

4 ответа

Я не уверен, что для этого есть что-то заранее подготовленное. Тем не менее, можно кодировать это:

import Data.IORef
import System.IO.Unsafe

track :: a -> IO (a, IO Bool)
track val = do
    ref <- newIORef False
    return
        ( unsafePerformIO (writeIORef ref True) `seq` val
        , readIORef ref
        )

Вот пример использования в ghci:

*NFTrack> (value, isEvaluated) <- track (undefined:undefined)
*NFTrack> isEvaluated
False
*NFTrack> case value of _:_ -> "neat!"
"neat!"
*NFTrack> isEvaluated
True

Конечно, это будет отслеживание того, оценивается ли завернутый блок записи-и-затем-возврата-исходного-значения в WHNF, а не то, передана ли вещь track оценивается как WHNF, так что вы захотите поместить это как можно ближе к интересующему вас thunk - например, он не сможет сказать вам, был ли кто-то еще оценил thunk, сделанный кем-то другим до начала отслеживания. И, конечно, подумайте об использовании MVar вместо IORef если вам нужна безопасность потоков.

Реализация ghci для :sprint в конечном итоге использует unpackClosure# из ghc-prim для проверки закрытия. Это может быть объединено со знанием формата объектов кучи, чтобы определить, было ли вычислено замыкание вплоть до нормальной формы слабой головы.

Есть несколько способов воспроизвести проверку, выполненную реализацией ghci для :sprint, GHC API выставляет getClosureData :: DynFlags -> a -> IO Closure в RtClosureInspect, Вакуумный пакет, который зависит только от ghc-prim, воспроизводит код из RtClosureInspect и выставляет getClosure :: a -> IO Closure, Не сразу очевидно, как проверить любой из этих Closure представления, например, следуют косвенному. Пакет ghc-heap-view проверяет замыкания и предоставляет как getClosureData :: a -> IO Closure и подробный вид Closure, ghc-heap-view зависит от API GHC.

Мы можем написать evaluated с точки зрения getBoxedClosureData из ghc-heap-view.

import GHC.HeapView

evaluated :: a -> IO Bool
evaluated = go . asBox
    where
        go box = do
            c <- getBoxedClosureData box
            case c of
                ThunkClosure     {} -> return False
                SelectorClosure  {} -> return False
                APClosure        {} -> return False
                APStackClosure   {} -> return False
                IndClosure       {indirectee = b'} -> go b'
                BlackholeClosure {indirectee = b'} -> go b'
                _ -> return True

Такое обращение с закрытием черной дыры может быть неправильным во время оценки черной дыры. Обработка закрытия селектора может быть неправильной. Предположение, что закрытия AP не находятся в нормальной форме со слабой головой, может быть неверным. Предположение, что все другие замыкания в WHNF почти наверняка неверны.

пример

Наш пример потребует двух параллельных потоков, чтобы наблюдать в одном потоке, что другой оценивает выражения.

import Data.Char
import Control.Concurrent

Мы можем передавать информацию боком из функции, не прибегая к чему-либо unsafe выборочно форсируя оценку. Следующее строит поток пар thunks, в которых мы можем выбрать форсировать одну или другую пару.

mkBitStream :: Integer -> [(Integer, Integer)]
mkBitStream a = (a+2, a+3) : mkBitStream (a+1)

zero заставляет первый и one заставляет второго.

zero :: [(x, y)] -> [(x, y)]
zero ((x, _):t) = x `seq` t

one :: [(x, y)] -> [(x, y)]
one ((_, y):t) = y `seq` t

copy является злой функцией идентификации, побочным эффектом которой является принудительное использование битов в потоке на основе проверки данных.

copy :: (a -> Bool) -> [(x, y)] -> [a] -> [a]
copy f bs []     = []
copy f bs (x:xs) = let bs' = if f x then one bs else zero bs
                   in bs' `seq` (x:copy f bs' xs)

readBs читает наш битовый поток, проверяя, был ли каждый из пар в паре evaluated,

readBs :: [(x, y)] -> IO ()
readBs bs@((f, t):bs') = do
    f' <- evaluated f
    if f'
    then putStrLn "0" >> readBs bs'
    else do
        t' <- evaluated t
        if t'
        then putStrLn "1" >> readBs bs'
        else readBs bs

форсирование copy при печати это имеет побочный эффект печати информации, наблюдаемой о прочитанной строке.

main = do
    let bs = mkBitStream 0
    forkIO (readBs bs)
    text <- getLine
    putStrLn (copy isAlpha bs text)
    getLine

Если мы запустим программу и предоставим вход abc123 мы наблюдаем побочный эффект, соответствующий проверке, если каждый из символов isAlpha

abc123
abc123
1
1
1
0
0
0

Отрицательный ответ, для протокола: не представляется возможным повторно использовать механизм sprintпотому что он тесно связан с интерпретируемой интерактивной оценкой, а не с примитивными структурами времени выполнения - насколько я могу судить; Я никогда не смотрел на внутренности GHC прежде.

Я начал с поиска "sprint" в источнике GHC на GitHub, который, как оказалось, делит реализацию с командой "print", но для Bool флаг называется forceи следовал определениям, пока я не попал в RtClosureInspect.cvObtainTerm, который представляется специализированным оценщиком.

Недавно было предложение, может быть, оно уже где-то реализовано https://mail.haskell.org/pipermail/libraries/2015-February/024917.html февраля/ 024917.html

Другие вопросы по тегам