-
Notifications
You must be signed in to change notification settings - Fork 459
Fold over Core without iter #200
Conversation
This uses fromScope to recur, and accumulates a continuation for the variables to avoid n² fmaps.
robrix
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ready for review.
| stripAnnotations (Var v) = Var v | ||
| stripAnnotations (Term t) | ||
| | Just c <- prj t, Ann _ b <- c = stripAnnotations b | ||
| | otherwise = Term (hmap stripAnnotations t) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is slightly less pretty than the iter definition, but is equivalent in effect and may even be a little faster (tho I certainly haven’t measured).
| | otherwise = Term t | ||
|
|
||
|
|
||
| instance Syntax Core where |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Goodbye!
| where bind (Ignored x) f = let x' = name x in (,) x' <$> local (x':) (getConst (unScope f)) | ||
| prettyCore :: Style -> Term Core User -> AnsiDoc | ||
| prettyCore style = run . runReader @Prec 0 . go (pure . name) | ||
| where go :: (Member (Reader Prec) sig, Carrier sig m) => (a -> m AnsiDoc) -> Term Core a -> m AnsiDoc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
go is a direct-recursive worker function, walking the Term via a helper continuation which we extend to deal with Incrs efficiently as we walk under binders.
|
|
||
| -- Annotations are not pretty-printed, as it lowers the signal/noise ratio too profoundly. | ||
| Ann _ c -> go var c | ||
| where bind (Ignored x) f = let x' = name x in (,) x' <$> go (incr (const (pure x')) var) (fromScope f) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the real meat of the change: we use fromScope to transform the Scope () f a (which is isomorphic to f (Incr () (f a))) to f (Incr () a), and then call go to recur over it with an extended continuation. I.e. if var :: a -> m AnsiDoc, then incr (const (pure x')) var :: Incr () a -> m AnsiDoc. Using this continuation enables us to avoid performing n² fmaps as we recur through the structure.
|
|
||
|
|
||
| interpret :: (Carrier sig m, Member eff sig, Syntax eff) => (forall a . Incr () (m a) -> m (Incr () (m a))) -> (a -> m b) -> Term eff a -> m b | ||
| interpret = iter id send |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to implement something like interpret again in the future, it should be easy enough since it shouldn’t actually require a Syntax instance to walk the Term direct-recursively, just an HFunctor instance.
This PR explores folds over
Corewithoutiter—direct recursion,fromScope, and so on.I’m sorry to be removing
iterandcata, but recursion schemes over nested datatypes require a bunch of extra machinery that I’d rather avoid.