packages feed

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

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE PolyKinds #-}

module Salmon.Op.OpGraph where

import Data.Functor.Classes
import Data.Kind (Type)
import Salmon.Op.Graph

-------------------------------------------------------------------------------

{- | An OpGraph is a complicated object.

- An OpGraph feels like a Comonad as it is centered on a node and has a recipe
  to find neighbors.
- An OpGraph feels like a program chunk because the recipe to find neighbors
  actually perfoms an effect.
- An OpGraph feels like a graph because the node on which is centered is
  somehow connected to a full graph of OpGraphs.

In Salmon, we want to define operations like "create a file" or "turn a server
up" as nodes.  It is really powerful to be able to see both operations
uniformly. However, to turn a server up, one needs many more steps than for
creating a file.
-}
data OpGraph (meval :: Type -> Type) node = OpGraph
    { predecessors :: meval (Graph (OpGraph meval node))
    , node :: node
    }
    deriving (Functor, Foldable, Traversable)

instance (Show a) => Show (OpGraph m a) where
    show gr = show gr.node

-------------------------------------------------------------------------------

-- | Injects a dependency so that op1 `inject` op2 is adding op2 as Connect-ed predecessor to op1
inject :: (Applicative m) => OpGraph m a -> OpGraph m a -> OpGraph m a
inject x y =
    x
        { predecessors = Connect <$> pure (Vertices [y]) <*> predecessors x
        }

-- | Injects a dependency so that op1 `inject` op2 is adding op2 as Overlay-ed predecessor to op1
overlaid :: (Applicative m) => OpGraph m a -> OpGraph m a -> OpGraph m a
overlaid x y =
    x
        { predecessors =
            Overlay <$> pure (Vertices [y]) <*> predecessors x
        }