hnix-0.17.0: src/Nix/Thunk/Basic.hs
{-# language ConstraintKinds #-}
{-# language UndecidableInstances #-}
{-# options_ghc -Wno-unused-do-bind #-}
module Nix.Thunk.Basic
( NThunkF(..)
, Deferred(..)
, deferred
, MonadBasicThunk
) where
import Nix.Prelude
import Control.Monad.Ref ( MonadRef(Ref, newRef, readRef, writeRef)
, MonadAtomicRef(atomicModifyRef)
)
import Control.Monad.Catch ( MonadCatch(..)
, MonadThrow(throwM)
)
import qualified Text.Show
import Nix.Thunk
-- * Data type @Deferred@
-- | Data is computed OR in a lazy thunk state which
-- is still not evaluated.
data Deferred m v = Computed v | Deferred (m v)
deriving (Functor, Foldable, Traversable)
-- ** Utils
-- | Apply second if @Deferred@, otherwise (@Computed@) - apply first.
-- Analog of @either@ for @Deferred = Computed|Deferred@.
deferred :: (v -> b) -> (m v -> b) -> Deferred m v -> b
deferred f1 f2 =
\case
Computed v -> f1 v
Deferred action -> f2 action
{-# inline deferred #-}
-- * Thunk references & lock handling
-- | Thunk resource reference (@ref-tf: Ref m@), and as such also also hold
-- a @Bool@ lock flag.
type ThunkRef m = Ref m Bool
-- | Reference (@ref-tf: Ref m v@) to a value that the thunk holds.
type ThunkValueRef m v = Ref m (Deferred m v)
-- | @ref-tf@ lock instruction for @Ref m@ (@ThunkRef@).
lockVal :: Bool -> (Bool, Bool)
lockVal = (True, )
-- | @ref-tf@ unlock instruction for @Ref m@ (@ThunkRef@).
unlockVal :: Bool -> (Bool, Bool)
unlockVal = (False, )
-- | Takes @ref-tf: Ref m@ reference, returns Bool result of the operation.
lock
:: ( MonadBasicThunk m
, MonadCatch m
)
=> ThunkRef m
-> m Bool
lock r = atomicModifyRef r lockVal
-- | Takes @ref-tf: Ref m@ reference, returns Bool result of the operation.
unlock
:: ( MonadBasicThunk m
, MonadCatch m
)
=> ThunkRef m
-> m Bool
unlock r = atomicModifyRef r unlockVal
-- * Data type for thunks: @NThunkF@
-- | The type of very basic thunks
data NThunkF m v =
Thunk (ThunkId m) (ThunkRef m) (ThunkValueRef m v)
instance (Eq v, Eq (ThunkId m)) => Eq (NThunkF m v) where
Thunk x _ _ == Thunk y _ _ = x == y
instance Show (NThunkF m v) where
show Thunk{} = toString thunkStubText
type MonadBasicThunk m = (MonadThunkId m, MonadAtomicRef m)
-- ** @instance MonadThunk NThunkF@
instance (MonadBasicThunk m, MonadCatch m)
=> MonadThunk (NThunkF m v) m v where
thunkId :: NThunkF m v -> ThunkId m
thunkId (Thunk n _ _) = n
thunk :: m v -> m (NThunkF m v)
thunk action =
do
freshThunkId <- freshId
liftA2 (Thunk freshThunkId)
(newRef False )
(newRef $ Deferred action)
query :: m v -> NThunkF m v -> m v
query vStub (Thunk _ _ lTValRef) =
deferred pure (const vStub) =<< readRef lTValRef
force :: NThunkF m v -> m v
force = forceMain
forceEff :: NThunkF m v -> m v
forceEff = forceMain
further :: NThunkF m v -> m (NThunkF m v)
further t@(Thunk _ _ ref) =
const (pure t) =<< atomicModifyRef ref dup
-- *** United body of `force*`
-- | Always returns computed @m v@.
--
-- Checks if resource is computed,
-- if not - with locking evaluates the resource.
forceMain
:: forall v m
. ( MonadBasicThunk m
, MonadCatch m
)
=> NThunkF m v
-> m v
forceMain (Thunk tIdV tRefV tValRefV) =
deferred pure computeW =<< readRef tValRefV
where
computeW :: m v -> m v
computeW vDefferred =
do
locked <- lock tRefV
bool
lockFailedV
(do
v <- vDefferred `catch` bindFailedW
writeRef tValRefV $ Computed v -- Proclaim value computed
unlockRef
pure v
)
$ not locked
where
lockFailedV :: m a
lockFailedV = throwM $ ThunkLoop $ show tIdV
bindFailedW :: SomeException -> m b
bindFailedW (e :: SomeException) =
do
unlockRef
throwM e
unlockRef :: m Bool
unlockRef = unlock tRefV
{-# inline forceMain #-} -- it is big function, but internal, and look at its use.
-- ** Kleisli functor HOFs: @instance MonadThunkF NThunkF@
instance (MonadBasicThunk m, MonadCatch m)
=> MonadThunkF (NThunkF m v) m v where
queryF
:: (v -> m r)
-> m r
-> NThunkF m v
-> m r
queryF k n (Thunk _ thunkRef thunkValRef) =
do
locked <- lock thunkRef
bool
n
go
(not locked)
where
go =
do
eres <- readRef thunkValRef
res <-
deferred
k
(const n)
eres
unlockRef
pure res
unlockRef = unlock thunkRef
forceF
:: (v -> m a)
-> NThunkF m v
-> m a
forceF k = k <=< force
forceEffF
:: (v -> m r)
-> NThunkF m v
-> m r
forceEffF k = k <=< forceEff
furtherF
:: (m v -> m v)
-> NThunkF m v
-> m (NThunkF m v)
furtherF k t@(Thunk _ _ ref) =
do
_modifiedIt <- atomicModifyRef ref $
\x ->
deferred
(const (x, x))
(\ d -> (Deferred (k d), x))
x
pure t