Проверьте, было ли значение оценено на слабую головную нормальную форму
В Haskell, можно ли проверить, оценивается ли значение для нормальной нормальной формы головы? Если функция уже существует, я ожидаю, что она будет иметь подпись типа
evaluated :: a -> IO Bool
Есть несколько мест, в которых работает аналогичная функциональность.
A предыдущий ответ познакомил меня с командой :sprint
ghci, который будет печатать только часть значения, которое уже было вынуждено для нормальной нормальной формы головы. :sprint
может наблюдать, было ли оценено значение:
> let l = ['a'..]
> :sprint l
l = _
> head l
'a'
> :sprint l
l = 'a' : _
В IO
возможно изучить свойства, которые в противном случае были бы запрещены. Например, можно сравнить в IO
, чтобы увидеть, были ли два значения взяты из одной и той же декларации. Это обеспечивается StableName
в System.Mem.StableName
и используется для решения проблемы наблюдаемого обмена в data-reify. Связанный StablePtr
не предоставляет механизм проверки, находится ли ссылочное значение в слабой нормальной форме головы.
Ответы
Ответ 1
Я не уверен, что для этого есть что-то готовое. Однако его можно закодировать:
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
Конечно, это будет отслеживать, обрабатывается ли завернутый файл write-and-then-return-the-original-value thunk WHNF, а не передается ли значение, переданное в track
, в WHNF, так что вы будете хотите поставить это как можно ближе к интересующему вас блоку - например он не сможет сказать вам, был ли кто-то другой, который был сделан кем-то другим, уже был оценен кем-то еще до начала отслеживания. И, конечно, используйте MVar
вместо IORef
, если вам нужна безопасность потоков.
Ответ 2
реализация ghci для :sprint
в конечном итоге использует unpackClosure#
из ghc-prim для проверки закрытие. Это можно объединить со знанием формата объектов кучи, чтобы определить, была ли оценка закрыта полностью до нормальной нормальной формы головы.
Существует несколько способов воспроизвести проверку, выполненную реализацией ghci, для :sprint
. GHC api предоставляет getClosureData :: DynFlags -> a -> IO Closure
в RtClosureInspect
. Пакет vacuum, который зависит только от ghc-prim, воспроизводит код из RtClosureInspect
и предоставляет getClosure :: a -> IO Closure
. Не сразу очевидно, как рассматривать любое из этих представлений Closure
, например, для обозначения косвенности. Пакет ghc-heap-view проверяет закрытие и предоставляет как getClosureData :: a -> IO Closure
, так и подробный вид Closure
. ghc-heap-view зависит от Apache 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
читает наш бит-поток, проверяя, был ли каждый из thunks в паре evaluated
.
readBs :: [(x, y)] -> IO ()
readBs [email protected]((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
Ответ 3
Отрицательный ответ для записи: не представляется возможным повторно использовать механизм sprint
, потому что он сильно привязан к интерпретируемой интерактивной оценке, а не к примитивным структурам времени исполнения - насколько я могу судить; Я никогда раньше не смотрел на внутренности GHC.
Я начал с поиска "sprint" в источнике GHC на GitHub, который, как оказалось, использует реализацию с командой "print", но для Bool
с именем force
и последующими определениями, пока я не доберусь до RtClosureInspect.cvObtainTerm, который, как представляется, является специализированным оценщиком.
Ответ 4
Недавно было предложение, возможно, оно уже реализовано уже https://mail.haskell.org/pipermail/libraries/2015-February/024917.html