summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Norell <ulf.norell@gmail.com>2022-01-13 12:45:47 (GMT)
committerUlf Norell <ulf.norell@gmail.com>2022-01-13 12:45:47 (GMT)
commit53dafe24117a8e039851a0f61ecc05b6998c6840 (patch)
treed5636459bdcdf644b422eb1dd8602b68cd89e8f3
parent60e2c7ae49e872b354db7d934ec2e16890947b93 (diff)
downloadagda-issue5722.zip
agda-issue5722.tar.gz
agda-issue5722.tar.bz2
[ #5722 ] experimental: add memoization of size lambdas at sizeInfrefs/pull/5735/headissue5722
-rw-r--r--src/full/Agda/TypeChecking/Reduce/Fast.hs151
1 files changed, 90 insertions, 61 deletions
diff --git a/src/full/Agda/TypeChecking/Reduce/Fast.hs b/src/full/Agda/TypeChecking/Reduce/Fast.hs
index 6fc2596..bc472a8 100644
--- a/src/full/Agda/TypeChecking/Reduce/Fast.hs
+++ b/src/full/Agda/TypeChecking/Reduce/Fast.hs
@@ -110,7 +110,7 @@ data CompactDefn
data BuiltinEnv = BuiltinEnv
{ bZero, bSuc, bTrue, bFalse, bRefl :: Maybe ConHead
- , bPrimForce, bPrimErase :: Maybe QName }
+ , bPrimForce, bPrimErase, bInf :: Maybe QName }
-- | Compute a 'CompactDef' from a regular definition.
compactDef :: BuiltinEnv -> Definition -> RewriteRules -> ReduceM CompactDef
@@ -421,6 +421,8 @@ fastReduce' :: Normalisation -> Term -> ReduceM (Blocked Term)
fastReduce' norm v = do
let name (Con c _ _) = c
name _ = __IMPOSSIBLE__
+ dname (Def d _) = d
+ dname _ = __IMPOSSIBLE__
zero <- fmap name <$> getBuiltin' builtinZero
suc <- fmap name <$> getBuiltin' builtinSuc
true <- fmap name <$> getBuiltin' builtinTrue
@@ -428,8 +430,9 @@ fastReduce' norm v = do
refl <- fmap name <$> getBuiltin' builtinRefl
force <- fmap primFunName <$> getPrimitive' "primForce"
erase <- fmap primFunName <$> getPrimitive' "primErase"
+ inf <- fmap dname <$> getBuiltin' builtinSizeInf
let bEnv = BuiltinEnv { bZero = zero, bSuc = suc, bTrue = true, bFalse = false, bRefl = refl,
- bPrimForce = force, bPrimErase = erase }
+ bPrimForce = force, bPrimErase = erase, bInf = inf }
allowedReductions <- asksTC envAllowedReductions
rwr <- optRewriting <$> pragmaOptions
constInfo <- unKleisli $ \f -> do
@@ -451,7 +454,7 @@ unKleisli f = ReduceM $ \ env x -> unReduceM (f x) env
-- spine of eliminations. Note that the environment doesn't necessarily bind all variables in the
-- term. The variables in the context in which the abstract machine is started are free in
-- closures. The 'IsValue' argument tracks whether the closure is in weak-head normal form.
-data Closure s = Closure IsValue Term (Env s) (Spine s)
+data Closure s = Closure IsValue Term (MemoInf s) (Env s) (Spine s)
-- ^ The environment applies to the 'Term' argument. The spine contains closures
-- with their own environments.
@@ -462,23 +465,29 @@ data IsValue = Value Blocked_ | Unevaled
-- | The spine is a list of eliminations. Application eliminations contain pointers.
type Spine s = [Elim' (Pointer s)]
+-- | Memoise the result of functions that take sizes applied to ∞.
+type MemoInf s = Maybe (Pointer s)
+
isValue :: Closure s -> IsValue
-isValue (Closure isV _ _ _) = isV
+isValue (Closure isV _ _ _ _) = isV
setIsValue :: IsValue -> Closure s -> Closure s
-setIsValue isV (Closure _ t env spine) = Closure isV t env spine
+setIsValue isV (Closure _ t memo env spine) = Closure isV t memo env spine
+
+setMemo :: MemoInf s -> Closure s -> Closure s
+setMemo memo (Closure v t _ env spine) = Closure v t memo env spine
-- | Apply a closure to a spine of eliminations. Note that this does not preserve the 'IsValue'
-- field.
clApply :: Closure s -> Spine s -> Closure s
clApply c [] = c
-clApply (Closure _ t env es) es' = Closure Unevaled t env (es <> es')
+clApply (Closure _ t memo env es) es' = Closure Unevaled t memo env (es <> es')
-- | Apply a closure to a spine, preserving the 'IsValue' field. Use with care, since usually
-- eliminations do not preserve the value status.
clApply_ :: Closure s -> Spine s -> Closure s
clApply_ c [] = c
-clApply_ (Closure b t env es) es' = Closure b t env (es <> es')
+clApply_ (Closure b t memo env es) es' = Closure b t memo env (es <> es')
-- * Pointers and thunks
@@ -524,7 +533,7 @@ blackHole ptr = writeSTRef ptr BlackHole
-- | Create a thunk. If the closure is a naked variable we can reuse the pointer from the
-- environment to avoid creating long pointer chains.
createThunk :: Closure s -> ST s (Pointer s)
-createThunk (Closure _ (Var x []) env spine)
+createThunk (Closure _ (Var x []) _ env spine)
| null spine, Just p <- lookupEnv x env = return p
createThunk cl = Pointer <$> newSTRef (Thunk cl)
@@ -688,7 +697,7 @@ data ControlFrame s = CaseK QName ArgInfo (FastCase FastCompiledClauses) (Spine
-- that free variables of the term are treated as constants by the abstract machine. If computing
-- full normal form we start off the control stack with a 'NormaliseK' continuation.
compile :: Normalisation -> Term -> AM s
-compile nf t = Eval (Closure Unevaled t emptyEnv []) [NormaliseK | nf == NF]
+compile nf t = Eval (Closure Unevaled t Nothing emptyEnv []) [NormaliseK | nf == NF]
decodePointer :: Pointer s -> ST s Term
decodePointer p = decodeClosure_ =<< derefPointer_ p
@@ -711,7 +720,7 @@ decodeClosure_ = ignoreBlocking <.> decodeClosure
-- reduction engine
-- * when there are rewrite rules to apply
decodeClosure :: Closure s -> ST s (Blocked Term)
-decodeClosure (Closure isV t env spine) = do
+decodeClosure (Closure isV t _ env spine) = do
vs <- decodeEnv env
es <- decodeSpine spine
return $ applyE (applySubst (parS vs) t) es <$ b
@@ -746,8 +755,8 @@ elimsToSpine env es = do
-- Going straight for a value for literals is mostly to make debug traces
-- less verbose and doesn't really buy anything performance-wise.
- closure _ t@Lit{} = Closure (Value $ notBlocked ()) t emptyEnv []
- closure fv t = env' `seq` Closure Unevaled t env' []
+ closure _ t@Lit{} = Closure (Value $ notBlocked ()) t Nothing emptyEnv []
+ closure fv t = env' `seq` Closure Unevaled t Nothing env' []
where env' = trimEnvironment fv env
-- | Trim unused entries from an environment. Currently only trims closed terms for performance
@@ -788,7 +797,7 @@ unusedPointerString = T.pack (show (withCurrentCallStack Impossible))
unusedPointer :: Pointer s
unusedPointer = Pure (Closure (Value $ notBlocked ())
- (Lit (LitString unusedPointerString)) emptyEnv [])
+ (Lit (LitString unusedPointerString)) Nothing emptyEnv [])
-- * Running the abstract machine
@@ -824,7 +833,7 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
| otherwise = const id
-- Checking for built-in zero and suc
- BuiltinEnv{ bZero = zero, bSuc = suc, bRefl = refl0 } = bEnv
+ BuiltinEnv{ bZero = zero, bSuc = suc, bRefl = refl0, bInf = inf } = bEnv
conNameId = nameId . qnameName . conName
isZero = case zero of
Nothing -> const False
@@ -832,6 +841,8 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
isSuc = case suc of
Nothing -> const False
Just s -> (conNameId s ==) . conNameId
+ isInf = maybe (const False) (\ i -> (nameId (qnameName i) ==) . nameId . qnameName) inf
+
-- If there's a non-standard equality (for instance doubly-indexed) we fall back to slow reduce
-- for primErase and "unbind" refl.
@@ -851,14 +862,14 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
runAM' :: AM s -> ST s (Blocked Term)
-- Base case: The focus is a value closure and the control stack is empty. Decode and return.
- runAM' (Eval cl@(Closure Value{} _ _ _) []) = decodeClosure cl
+ runAM' (Eval cl@(Closure Value{} _ _ _ _) []) = decodeClosure cl
-- Unevaluated closure: inspect the term and take the appropriate action. For instance,
-- - Change to the 'Match' state if a definition
-- - Look up in the environment if variable
-- - Perform a beta step if lambda and application elimination in the spine
-- - Perform a record beta step if record constructor and projection elimination in the spine
- runAM' s@(Eval cl@(Closure Unevaled t env spine) !ctrl) = {-# SCC "runAM.Eval" #-}
+ runAM' s@(Eval cl@(Closure Unevaled t memo env spine) !ctrl) = {-# SCC "runAM.Eval" #-}
case t of
-- Case: definition. Enter 'Match' state if defined function or shift to evaluating an
@@ -927,17 +938,23 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
Just p -> evalPointerAM p spine ctrl
-- Case: lambda. Perform the beta reduction if applied. Otherwise it's a value.
- Lam h b ->
- case spine of
- [] -> runAM done
- elim : spine' ->
- case b of
- Abs _ b -> runAM (evalClosure b (getArg elim `extendEnv` env) spine' ctrl)
- NoAbs _ b -> runAM (evalClosure b env spine' ctrl)
+ Lam h b | Just v <- memo,
+ Apply p : spine <- spine -> derefPointer (unArg p) >>= \ case
+ Thunk (Closure _ (Def inf []) _ _ []) | isInf inf -> evalPointerAM v spine ctrl
+ _ -> fallback
+ | otherwise -> fallback
where
- getArg (Apply v) = unArg v
- getArg (IApply _ _ v) = v
- getArg Proj{} = __IMPOSSIBLE__
+ fallback =
+ case spine of
+ [] -> runAM done
+ elim : spine' ->
+ case b of
+ Abs _ b -> runAM (evalClosure b (getArg elim `extendEnv` env) spine' ctrl)
+ NoAbs _ b -> runAM (evalClosure b env spine' ctrl)
+ where
+ getArg (Apply v) = unArg v
+ getArg (IApply _ _ v) = v
+ getArg Proj{} = __IMPOSSIBLE__
-- Case: values. Literals and function types are already in weak-head normal form.
-- We throw away the environment for literals mostly to make debug printing less verbose.
@@ -978,12 +995,12 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
-- If the current focus is a value closure, we look at the control stack.
-- Case NormaliseK: The focus is a weak-head value that should be fully normalised.
- runAM' s@(Eval cl@(Closure b t env spine) (NormaliseK : ctrl)) =
+ runAM' s@(Eval cl@(Closure b t _ env spine) (NormaliseK : ctrl)) = -- TODO: preserve the memo table?
case t of
- Def _ [] -> normaliseArgsAM (Closure b t emptyEnv []) spine ctrl
- Con _ _ [] -> normaliseArgsAM (Closure b t emptyEnv []) spine ctrl
- Var _ [] -> normaliseArgsAM (Closure b t emptyEnv []) spine ctrl
- MetaV _ [] -> normaliseArgsAM (Closure b t emptyEnv []) spine ctrl
+ Def _ [] -> normaliseArgsAM (Closure b t Nothing emptyEnv []) spine ctrl
+ Con _ _ [] -> normaliseArgsAM (Closure b t Nothing emptyEnv []) spine ctrl
+ Var _ [] -> normaliseArgsAM (Closure b t Nothing emptyEnv []) spine ctrl
+ MetaV _ [] -> normaliseArgsAM (Closure b t Nothing emptyEnv []) spine ctrl
Lit{} -> runAM done
@@ -998,7 +1015,7 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
where done = Eval (mkValue (notBlocked ()) cl) ctrl
shiftElims t env0 env es = do
spine' <- elimsToSpine env es
- runAM (Eval (Closure b t env0 (spine' <> spine)) (NormaliseK : ctrl))
+ runAM (Eval (Closure b t Nothing env0 (spine' <> spine)) (NormaliseK : ctrl))
-- Case: ArgK: We successfully normalised an argument. Start on the next argument, or if there
-- isn't one we're done.
@@ -1010,7 +1027,7 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
-- Case: NatSucK m
-- If literal add m to the literal,
- runAM' (Eval cl@(Closure Value{} (Lit (LitNat n)) _ _) (NatSucK m : ctrl)) =
+ runAM' (Eval cl@(Closure Value{} (Lit (LitNat n)) _ _ _) (NatSucK m : ctrl)) =
runAM (evalTrueValue (Lit $! LitNat $! m + n) emptyEnv [] ctrl)
-- otherwise apply 'suc' m times.
@@ -1027,13 +1044,13 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
-- If literal apply the primitive function if no more arguments, otherwise
-- store the literal in the continuation and evaluate the next argument.
- runAM' (Eval (Closure _ (Lit a) _ _) (PrimOpK f op vs es cc : ctrl)) =
+ runAM' (Eval (Closure _ (Lit a) _ _ _) (PrimOpK f op vs es cc : ctrl)) =
case es of
[] -> runAM (evalTrueValue (op (a : vs)) emptyEnv [] ctrl)
e : es' -> evalPointerAM e [] (PrimOpK f op (a : vs) es' cc : ctrl)
-- If not a literal we use the case tree if there is one, otherwise we are stuck.
- runAM' (Eval cl@(Closure (Value blk) _ _ _) (PrimOpK f _ vs es mcc : ctrl)) =
+ runAM' (Eval cl@(Closure (Value blk) _ _ _ _) (PrimOpK f _ vs es mcc : ctrl)) =
case mcc of
Nothing -> rewriteAM (Eval stuck ctrl)
Just cc -> runAM (Match f cc spine ([] :> notstuck) ctrl)
@@ -1041,14 +1058,14 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
p = pureThunk cl
lits = map (pureThunk . litClos) (reverse vs)
spine = fmap (Apply . defaultArg) $ lits <> [p] <> es
- stuck = Closure (Value blk) (Def f []) emptyEnv spine
- notstuck = Closure Unevaled (Def f []) emptyEnv spine
+ stuck = Closure (Value blk) (Def f []) Nothing emptyEnv spine
+ notstuck = Closure Unevaled (Def f []) Nothing emptyEnv spine
litClos l = trueValue (Lit l) emptyEnv []
-- Case: ForceK. Here we need to check if the argument is a canonical form (i.e. not a variable
-- or stuck function call) and if so apply the function argument to the value. If it's not
-- canonical we are stuck.
- runAM' (Eval arg@(Closure (Value blk) t _ _) (ForceK pf spine0 spine1 : ctrl))
+ runAM' (Eval arg@(Closure (Value blk) t _ _ _) (ForceK pf spine0 spine1 : ctrl))
| isCanonical t =
case spine1 of
Apply k : spine' ->
@@ -1062,7 +1079,7 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
argPtr = pureThunk arg
elim = Apply (defaultArg argPtr)
spine' = spine0 <> [elim] <> spine1
- stuck = Closure (Value blk) (Def pf []) emptyEnv spine'
+ stuck = Closure (Value blk) (Def pf []) Nothing emptyEnv spine'
isCanonical = \case
Lit{} -> True
@@ -1081,26 +1098,37 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
-- Case: EraseK. We evaluate both arguments to values, then do a simple check for the easy
-- cases and otherwise fall back to slow reduce.
- runAM' (Eval cl2@(Closure Value{} arg2 _ _) (EraseK f spine0 [Apply p1] _ spine3 : ctrl)) = do
- cl1@(Closure _ arg1 _ sp1) <- derefPointer_ (unArg p1)
+ runAM' (Eval cl2@(Closure Value{} arg2 _ _ _) (EraseK f spine0 [Apply p1] _ spine3 : ctrl)) = do
+ cl1@(Closure _ arg1 _ _ sp1) <- derefPointer_ (unArg p1)
case (arg1, arg2) of
(Lit l1, Lit l2) | l1 == l2, isJust refl ->
runAM (evalTrueValue (Con (fromJust refl) ConOSystem []) emptyEnv [] ctrl)
_ ->
let spine = spine0 ++ map (Apply . hide . defaultArg . pureThunk) [cl1, cl2] ++ spine3 in
fallbackAM (evalClosure (Def f []) emptyEnv spine ctrl)
- runAM' (Eval cl1@(Closure Value{} _ _ _) (EraseK f spine0 [] [Apply p2] spine3 : ctrl)) =
+ runAM' (Eval cl1@(Closure Value{} _ _ _ _) (EraseK f spine0 [] [Apply p2] spine3 : ctrl)) =
evalPointerAM (unArg p2) [] (EraseK f spine0 [Apply $ hide $ defaultArg $ pureThunk cl1] [] spine3 : ctrl)
runAM' (Eval _ (EraseK{} : _)) =
__IMPOSSIBLE__
-- Case: UpdateThunk. Write the value to the pointers in the UpdateThunk frame.
- runAM' (Eval cl@(Closure Value{} _ _ _) (UpdateThunk ps : ctrl)) =
- mapM_ (`storePointer` cl) ps >> runAM (Eval cl ctrl)
-
- -- Case: ApplyK. Application after thunk update. Add the spine from the control frame to the
- -- closure.
- runAM' (Eval cl@(Closure Value{} _ _ _) (ApplyK spine : ctrl)) =
+ runAM' (Eval cl@(Closure Value{} t memo env spine) (UpdateThunk ps : ctrl)) = do
+ case ctrl of
+ ApplyK (Apply p : args) : ctrl
+ | Nothing <- memo,
+ [] <- spine ->
+ derefPointer (unArg p) >>= \ case
+ Thunk (Closure _ (Def inf []) _ _ []) | isInf inf -> do
+ q <- createThunk (Closure Unevaled t Nothing env [Apply p])
+ mapM_ (`storePointer` setMemo (Just q) cl) ps
+ evalPointerAM q args ctrl
+ _ -> cont
+ _ -> cont
+ where cont = mapM_ (`storePointer` cl) ps >> runAM (Eval cl ctrl)
+
+ -- Case: ApplyK. Application after thunk update. Add the spine from the control frame
+ -- to the closure.
+ runAM' (Eval cl@(Closure Value{} _ _ _ _) (ApplyK spine : ctrl)) =
runAM (Eval (clApply cl spine) ctrl)
-- Case: CaseK. Pattern matching against a value. If it's a stuck value the pattern match is
@@ -1109,7 +1137,7 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
-- a CatchAll in the match stack, or fail if there isn't one (see failedMatch). If the current
-- branches contain a catch-all case we need to push a CatchAll on the match stack if picking
-- one of the other branches.
- runAM' (Eval cl@(Closure (Value blk) t env spine) ctrl0@(CaseK f i bs spine0 spine1 stack : ctrl)) =
+ runAM' (Eval cl@(Closure (Value blk) t _ env spine) ctrl0@(CaseK f i bs spine0 spine1 stack : ctrl)) =
{-# SCC "runAM.CaseK" #-}
case blk of
Blocked{} | null [()|Con{} <- [t]] -> stuck -- we might as well check the blocking tag first
@@ -1213,7 +1241,7 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
FDone xs body -> do
-- Don't ask me why, but not being strict in the spine causes a memory leak.
let (zs, env, !spine') = buildEnv xs spine
- runAM (Eval (Closure Unevaled (lams zs body) env spine') ctrl)
+ runAM (Eval (Closure Unevaled (lams zs body) Nothing env spine') ctrl)
-- A record pattern match. This does not block evaluation (since that would violate eta
-- equality), so in this case we replace the argument with its projections in the spine and
@@ -1224,7 +1252,7 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
(spine0, Apply e : spine1) -> do -- rewriting or 'with'.
-- Replace e by its projections in the spine. And don't forget a
-- CatchAll frame if there's a catch-all.
- let projClosure (Arg ai f) = Closure Unevaled (Var 0 []) (extendEnv (unArg e) emptyEnv) [Proj ProjSystem f]
+ let projClosure (Arg ai f) = Closure Unevaled (Var 0 []) Nothing (extendEnv (unArg e) emptyEnv) [Proj ProjSystem f]
projs <- mapM (createThunk . projClosure) fs
let spine' = spine0 <> map (Apply . defaultArg) projs <> spine1
stack' = caseMaybe ca stack $ \ cc -> CatchAll cc spine >: stack
@@ -1264,7 +1292,7 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
evalPointerAM (Pure cl) spine ctrl = runAM (Eval (clApply cl spine) ctrl)
evalPointerAM (Pointer p) spine ctrl = readPointer p >>= \ case
BlackHole -> __IMPOSSIBLE__
- Thunk cl@(Closure Unevaled _ _ _) | callByNeed -> do
+ Thunk cl@(Closure Unevaled _ _ _ _) | callByNeed -> do
blackHole p
runAM (Eval cl $ updateThunkCtrl p $ [ApplyK spine | not (null spine)] ++ ctrl)
Thunk cl -> runAM (Eval (clApply cl spine) ctrl)
@@ -1322,10 +1350,10 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
-- rewriting and pack the result back up in a closure. In case some rewrite rules actually fired
-- the next state is an unevaluated closure, otherwise it's a value closure.
rewriteAM' :: AM s -> ST s (Blocked Term)
- rewriteAM' s@(Eval (Closure (Value blk) t env spine) ctrl)
+ rewriteAM' s@(Eval (Closure (Value blk) t _ env spine) ctrl)
| null rewr = runAM s
| otherwise = traceDoc ("R" <+> pretty s) $ do
- v0 <- decodeClosure_ (Closure Unevaled t env [])
+ v0 <- decodeClosure_ (Closure Unevaled t Nothing env [])
es <- decodeSpine spine
case runReduce (rewrite blk (applyE v0) rewr es) of
NoReduction b -> runAM (evalValue (() <$ b) (ignoreBlocking b) emptyEnv [] ctrl)
@@ -1380,11 +1408,11 @@ reduceTm rEnv bEnv !constInfo normalisation ReductionFlags{..} =
__IMPOSSIBLE__
-- Some helper functions to build machine states and closures.
- evalClosure t env spine = Eval (Closure Unevaled t env spine)
- evalValue b t env spine = Eval (Closure (Value b) t env spine)
- evalTrueValue = evalValue $ notBlocked ()
- trueValue t env spine = Closure (Value $ notBlocked ()) t env spine
- mkValue b = setIsValue (Value b)
+ evalClosure t env spine = Eval (Closure Unevaled t Nothing env spine)
+ evalValue b t env spine = Eval (Closure (Value b) t Nothing env spine)
+ evalTrueValue t env spine = evalValue (notBlocked ()) t env spine
+ trueValue t env spine = Closure (Value $ notBlocked ()) t Nothing env spine
+ mkValue b = setIsValue (Value b)
-- Building lambdas
lams :: [Arg String] -> Term -> Term
@@ -1427,11 +1455,12 @@ instance Pretty (Pointer s) where
prettyPrec p = prettyPrec p . unsafeDerefPointer
instance Pretty (Closure s) where
- prettyPrec _ (Closure Value{} (Lit (LitString unused)) _ _)
+ prettyPrec _ (Closure Value{} (Lit (LitString unused)) _ _ _)
| unused == unusedPointerString = "_"
- prettyPrec p (Closure isV t env spine) =
+ prettyPrec p (Closure isV t memo env spine) =
mparens (p > 9) $ fsep [ text tag
, nest 2 $ prettyPrec 10 t
+ , maybe empty (\ m -> "memo:" <> prettyPrec 10 m) memo
, nest 2 $ prettyList $ zipWith envEntry [0..] (envToList env)
, nest 2 $ prettyList spine ]
where envEntry i c = text ("@" ++ show i ++ " =") <+> pretty c