packages feed

essence-of-live-coding-0.2.7: src/LiveCoding/Migrate/NoMigration.hs

{-# LANGUAGE Arrows #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE RecordWildCards #-}

{- |
Module      : LiveCoding.Migrate.NoMigration
Description : Mechanism to save state in a Cell without requiring a Data instance.

If a data type is wrapped in 'NoMigration' then it can be used as the state of a 'Cell'
without requiring it to have a 'Data' instance. The consequence is that if the type has changed
in between a livereload, then the previous saved value will be discarded, and no migration attempt
will happen.

'LiveCoding' does not export 'delay' and 'changes' from this module. These functions should be
used with a qualified import.
-}
module LiveCoding.Migrate.NoMigration where

-- base

import Control.Arrow (Arrow (arr, second), returnA, (>>>))
import Control.Monad (guard)
import Data.Data (
  Constr,
  Data (dataTypeOf, gunfold, toConstr),
  DataType,
  Fixity (Prefix),
  Typeable,
  mkConstr,
  mkDataType,
 )

-- essence-of-live-coding
import LiveCoding.Cell
import qualified LiveCoding.Cell.Feedback as Feedback
import LiveCoding.Cell.Monad (hoistCellKleisli)

-- * 'NoMigration' data type and 'Data' instance.

{- | Isomorphic to @'Maybe' a@ but has a different 'Data' instance. The 'Data' instance for @'NoMigration' a@ doesn't require a 'Data' instance for @a@.

 If a data type is wrapped in 'NoMigration' then it can be used as the state of a 'Cell'
 without requiring it to have a 'Data' instance. The consequence is that if the type has changed
 in between a livereload, then the previous saved value will be discarded, and no migration attempt
 will happen.
-}
data NoMigration a = Initialized a | Uninitialized
  deriving (Show, Eq, Functor, Foldable, Traversable)

fromNoMigration :: a -> NoMigration a -> a
fromNoMigration _ (Initialized a) = a
fromNoMigration a Uninitialized = a

dataTypeNoMigration :: DataType
dataTypeNoMigration = mkDataType "NoMigration" [initializedConstr, uninitializedConstr]

initializedConstr :: Constr
initializedConstr = mkConstr dataTypeNoMigration "Initialized" [] Prefix

uninitializedConstr :: Constr
uninitializedConstr = mkConstr dataTypeNoMigration "Uninitialized" [] Prefix

-- |The Data instance for @'NoMigration' a@ doesn't require a 'Data' instance for @a@.
instance (Typeable a) => Data (NoMigration a) where
  dataTypeOf _ = dataTypeNoMigration
  toConstr (Initialized _) = initializedConstr
  toConstr Uninitialized = uninitializedConstr
  gunfold _cons nil _ = nil Uninitialized

-- * Utility functions which internally use 'NoMigration'.

{- | Like 'Feedback.delay', but doesn't require 'Data' instance, and only migrates the
 last value if it still has the same type.
-}
delay :: (Monad m, Typeable a) => a -> Cell m a a
delay a = arr Initialized >>> Feedback.delay Uninitialized >>> arr (fromNoMigration a)

{- | Like 'Utils.changes', but doesn't require Data instance, and only migrates the last
 value if it still is of the same type.
-}
changes :: (Typeable a, Eq a, Monad m) => Cell m a (Maybe a)
changes = proc a -> do
  aLast <- delay Nothing -< Just a
  returnA
    -< do
      aLast' <- aLast
      guard $ a /= aLast'
      return a

{- | Caching version of 'arrM'.

   Only runs the computation in @m@ when the input value
   changes. Meanwhile it keeps outputing the last outputted value. Also runs the computation
   on the first tick. Does not require 'Data' instance. On `:livereload` will run action again on
   first tick.
-}
arrChangesM :: (Monad m, Typeable a, Typeable b, Eq a) => (a -> m b) -> Cell m a b
arrChangesM f = Cell {cellState = Uninitialized, ..}
  where
    cellStep Uninitialized a = h a
    cellStep (Initialized (a', b)) a =
      if a == a'
        then return (b, Initialized (a, b))
        else h a
    h a = (\b' -> (b', Initialized (a, b'))) <$> f a

cellNoMigration :: (Typeable s, Functor m) => s -> (s -> a -> m (b, s)) -> Cell m a b
cellNoMigration state step = Cell {cellState = Uninitialized, ..}
  where
    cellStep Uninitialized a = second Initialized <$> step state a
    cellStep (Initialized s) a = second Initialized <$> step s a