packages feed

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

-- A contravariant functor to track dependencies in a "serializer" style:
-- you defined basic nodes and then compose them.
module Salmon.Op.Track where

import Data.Functor.Contravariant (Contravariant (..), (>$<))
import Data.Functor.Contravariant.Divisible (Divisible (..), divided)
import Salmon.Op.Graph
import Salmon.Op.OpGraph

(>*<) :: (Divisible f) => f a -> f b -> f (a, b)
(>*<) = divided

infixr 5 >*<

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

{- | A Track is a promise to make an OpGraph by consuming a given item.

In Salmon, a Track is a mechanism to say "yeah, if you need an database, I have a way to get you one".
Track has nice properties, by virtue of being a Contravariant and Divisible functor.

Track also has nice combinators, by virtue of producing an OpGraph, which is a
complex comonadic-ish object with combinators.
-}
newtype Track m n a
    = Track {run :: a -> OpGraph m n}

instance Contravariant (Track m n) where
    contramap f s = Track (run s . f)

instance (Applicative m, Monoid n) => Divisible (Track m n) where
    conquer = Track (const $ OpGraph (pure $ Vertices []) mempty)
    divide f t1 t2 = Track $ \a ->
        let
            (h, k) = f a
            x = run t1 h
            y = run t2 k
         in
            OpGraph (Vertices <$> pure [x, y]) mempty

{- | A function to inject a dependency form a tracer when generating an OpGraph.
At first it looks like the we could just directly apply.
-}
tracking ::
    (Applicative m) =>
    Track m n z ->
    (a -> (b, z)) ->
    a ->
    (b -> OpGraph m n) ->
    OpGraph m n
tracking t f arg use =
    let (b, z) = f arg
     in use b `inject` run t z

data Tracked m n a
    = Tracked
    { track :: Track m n a
    , obj :: a
    }

-- | A pure tracked merely is the constructor with some initial trace.
pureTracked :: Track m n a -> a -> Tracked m n a
pureTracked = Tracked

-- | Eval the OpGraph of a Tracked object.
trackedGraph :: Tracked m n a -> OpGraph m n
trackedGraph t = run t.track t.obj

-- | a quasi-functor which records all dependencies at the point of mapping
mapTracked :: (a -> b) -> Tracked m n a -> Tracked m n b
mapTracked f t = Tracked (Track $ const $ trackedGraph t) (f t.obj)

-- | a quasi-applicative which records all combined dependencies at the point of mapping
apTracked :: (Applicative m) => Tracked m n (a -> b) -> Tracked m n a -> Tracked m n b
apTracked tf ta = Tracked (Track $ const $ trackedGraph tf `overlaid` trackedGraph ta) (tf.obj ta.obj)

-- | a quasi-monad which records previous dependencies as predecessors before binding
bindTracked :: (Applicative m) => Tracked m n a -> (a -> Tracked m n b) -> Tracked m n b
bindTracked ta f = Tracked (Track $ const $ trackedGraph tb `inject` trackedGraph ta) b
  where
    tb@(Tracked _ b) = f ta.obj

-- | A similar to `tracking` but for a Tracked object.
using :: (Applicative m) => Tracked m n a -> (a -> OpGraph m n) -> OpGraph m n
using tracked use =
    tracking tracked.track dup tracked.obj use
  where
    dup a = (a, a)

{- | Like 'using', but for two independent 'Tracked' values at once — avoids
nesting two 'using' calls just to get both objects in scope together.
-}
using2 ::
    (Applicative m) =>
    Tracked m n a ->
    Tracked m n b ->
    ((a, b) -> OpGraph m n) ->
    OpGraph m n
using2 t1 t2 use =
    use (t1.obj, t2.obj) `inject` (trackedGraph t1 `overlaid` trackedGraph t2)

-- | Like 'using2', for three independent 'Tracked' values.
using3 ::
    (Applicative m) =>
    Tracked m n a ->
    Tracked m n b ->
    Tracked m n c ->
    ((a, b, c) -> OpGraph m n) ->
    OpGraph m n
using3 t1 t2 t3 use =
    use (t1.obj, t2.obj, t3.obj) `inject` (trackedGraph t1 `overlaid` trackedGraph t2 `overlaid` trackedGraph t3)