packages feed

salmon-core-0.1.0.0: src/Salmon/Op/GraphFold.hs

{-# LANGUAGE ScopedTypeVariables #-}

{- | Generic traversal primitives over 'Cofree' 'Graph' trees.

'Graph' already derives 'Foldable'/'Functor'/'Traversable', which gives
pre-order traversal for free (see 'Data.Foldable.toList'). 'foldWithContext'
covers the shape pre-order alone doesn't: a fold that carries context down
from ancestor to descendant while remembering which 'Graph' constructor
connected them. Generic over any node type and knows nothing about ops,
actions, or refs — that stays in the caller.

This module used to also offer a post-order visitor
(@postOrderM@), written for "Salmon.Actions.UpDown".'Salmon.Actions.UpDown.upTree'
to propagate "a predecessor failed, so skip me too" down a subtree. Milestone
4 of @specs\/per-node-state-machines.md@ moved both drivers onto
"Salmon.Op.Dag", which answers that question from the collapsed magma
instead of a tree walk, and nothing else in this repository ever called it —
dropped rather than kept speculative; recover it from history if a caller
needs it again.
-}
module Salmon.Op.GraphFold (
    Shape (..),
    Branch (..),
    foldWithContext,
) where

import Control.Comonad.Cofree (Cofree (..))

import Salmon.Op.Graph

-- | Which 'Graph' constructor a node's own predecessors are wrapped in.
data Shape = SVertices | SOverlay | SConnect
    deriving (Show, Eq, Ord)

-- | Which arm of its parent's 'Graph' constructor a child was reached
-- through.
data Branch
    = FromVertices
    | FromOverlayL
    | FromOverlayR
    | FromConnectL
    | FromConnectR
    deriving (Show, Eq, Ord)

{- | Walk a 'Cofree' 'Graph', calling @onNode ctx shape x@ at every node
(told the inherited context and the 'Shape' of its own predecessor graph),
and folding results with '(<>)'. @nextCtx@ computes the context handed down
to a child, told which 'Branch' connects the current node to that child —
this is how e.g. "skip through nodes with no real payload" is implemented by
callers: return the unchanged @ctx@ instead of a new one.
-}
foldWithContext ::
    forall a ctx r.
    (Monoid r) =>
    ctx ->
    (ctx -> Shape -> a -> r) ->
    (ctx -> Branch -> a -> ctx) ->
    Cofree Graph a ->
    r
foldWithContext ctx0 onNode nextCtx = go ctx0
  where
    go :: ctx -> Cofree Graph a -> r
    go ctx (x :< gr) = onNode ctx (shapeOf gr) x <> descend ctx x gr

    shapeOf :: Graph b -> Shape
    shapeOf (Vertices _) = SVertices
    shapeOf (Overlay _ _) = SOverlay
    shapeOf (Connect _ _) = SConnect

    descend :: ctx -> a -> Graph (Cofree Graph a) -> r
    descend ctx x (Vertices cs) = foldMap (go (nextCtx ctx FromVertices x)) cs
    descend ctx x (Overlay c1 c2) =
        foldMap (go (nextCtx ctx FromOverlayL x)) c1
            <> foldMap (go (nextCtx ctx FromOverlayR x)) c2
    descend ctx x (Connect c1 c2) =
        foldMap (go (nextCtx ctx FromConnectL x)) c1
            <> foldMap (go (nextCtx ctx FromConnectR x)) c2