packages feed

reflex-potatoes-0.1.0.0: src/Reflex/Potato/Helpers.hs

--TODO move some of this stuff into it's own lib when it's properly tested

{-# LANGUAGE RecursiveDo #-}




module Reflex.Potato.Helpers
  (
  -- other helpers
    dsum_to_dmap


  -- reflex helpers
  , simultaneous
  , assertEvent
  , assertEventWith
  , fmapMaybeWarn
  , fmapMaybeWarnWith
  , traceEventSimple
  , leftmostWarn
  , leftmostAssert
  , leftmostWarnWithIndex
  , leftmostWarnWithEverything
  , alignWarn
  , alignAssert
  , foldDynMergeWith
  , foldDynMerge
  , fanDSum
  , pushAlwaysDyn

  , delayEvent
  , sequenceEvents
  , stepEvents
  , stepEventsAndCollectOutput
  , stepEventsAndSequenceCollectOutput

  , switchHoldPair
  , switchHoldTriple

  , waitForSecondAfterFirst
  )
where

import           Prelude            (error)
import           Relude             hiding (error)

import           Reflex

import           Control.Monad.Fix

import           Data.Align
import qualified Data.Dependent.Map as DM
import qualified Data.Dependent.Sum as DS
import qualified Data.GADT.Compare  as DM
import           Data.These

-- | fires only when both events fire
simultaneous :: (Reflex t) => Event t a -> Event t b -> Event t (a,b)
simultaneous eva evb = alignEventWithMaybe
  (\case
    These a b -> Just (a,b)
    _ -> Nothing)
  eva
  evb

dsum_to_dmap :: DM.GCompare k => DS.DSum k f -> DM.DMap k f
dsum_to_dmap ds = DM.fromList [ds]


{- TODO can't get -fno-ignore-asserts to work
-- | assert that a predicate is true each time the event triggers
-- internall calls assert, which can be disabled via compiler options
-- enable explicitly with {-# OPTIONS_GHC -fno-ignore-asserts #-}
assertEvent :: (Reflex t, Show a)
  => String -- ^ assert message
  -> (a -> Bool) -- ^ predicate to check
  -> Event t a
  -> Event t a
assertEvent s p = fmap (\x -> byPred assert s p x x)

-- | assert that a predicate is true each time the event triggers
-- internall calls assert, which can be disabled via compiler options
-- enable explicitly with {-# OPTIONS_GHC -fno-ignore-asserts #-}
assertEventWith :: (Reflex t)
  => (a -> String) -- ^ assert message
  -> (a -> Bool) -- ^ predicate to check
  -> Event t a
  -> Event t a
assertEventWith sf p = fmap (\x -> byPred assert (sf x) id (p x) x)
-}

-- | assert that a predicate is true each time the event triggers
assertEvent :: (Reflex t, Show a)
  => String -- ^ assert message
  -> (a -> Bool) -- ^ predicate to check
  -> Event t a
  -> Event t a
assertEvent s p = fmap (\x -> if not (p x) then error $ s <> " " <> show x else x)

-- | assert that a predicate is true each time the event triggers
assertEventWith :: (Reflex t)
  => (a -> String) -- ^ assert message
  -> (a -> Bool) -- ^ predicate to check
  -> Event t a
  -> Event t a
assertEventWith sf p = fmap (\x -> if not (p x) then error $ sf x else x)

-- | same as fmapMaybe except outputs a warning if predicate fails
fmapMaybeWarn :: (Reflex t, Show a)
  => String -- ^ warning message
  -> (a -> Bool) -- ^ predicate to check
  -> Event t a
  -> Event t a
fmapMaybeWarn s p ev = r where
  ev' = fmap (\x -> (p x, x)) ev
  good = fmapMaybe (\(a,x) -> if a then Just x else Nothing) ev'
  bad =  fmapMaybe (\(a,x) -> if not a then Just x else Nothing) ev'
  r = leftmost [good, fmapMaybe (const Nothing) $ traceEvent s bad]

-- | same as fmapMaybe except outputs a warning if predicate fails
fmapMaybeWarnWith :: (Reflex t)
  => (a -> String) -- ^ warning message
  -> (a -> Bool) -- ^ predicate to check
  -> Event t a
  -> Event t a
fmapMaybeWarnWith sf p ev = r where
  ev' = fmap (\x -> (p x, x)) ev
  good = fmapMaybe (\(a,x) -> if a then Just x else Nothing) ev'
  bad =  fmapMaybe (\(a,x) -> if not a then Just x else Nothing) ev'
  r = leftmost [good, fmapMaybe (const Nothing) $ traceEventWith sf bad]

traceEventSimple :: (Reflex t) => String -> Event t a -> Event t a
traceEventSimple s = traceEventWith (const s)

-- | same as leftmost but outputs a warning if more than one event fires at once
leftmostWarn :: (Reflex t) => String -> [Event t a] -> Event t a
leftmostWarn label evs = r where
  combine = mergeList evs
  nowarn =
    fmapMaybe (\x -> if length x == 1 then Just (head x) else Nothing) combine
  warn =
    traceEventWith
        (const ("WARNING: multiple " <> label <> " events triggered"))
      $ fmapMaybe (\x -> if length x > 1 then Just (head x) else Nothing)
                  combine
  r = leftmost [nowarn, warn]

-- | same as leftmost but asserts if more than one event fires at once
leftmostAssert :: (Reflex t) => String -> [Event t a] -> Event t a
leftmostAssert label evs = r where
  combine = mergeList evs
  nowarn =
    fmapMaybe (\x -> if length x == 1 then Just (head x) else Nothing) combine
  warn =
    assertEventWith (const ("ASSERT: multiple " <> label <> " events triggered")) (const False)
      $ fmapMaybe (\x -> if length x > 1 then Just (head x) else Nothing)
                  combine
  r = leftmost [nowarn, warn]

-- | same as leftmostWarn but also adds an index for debugging
leftmostWarnWithIndex :: (Reflex t) => String -> [Event t a] -> Event t a
leftmostWarnWithIndex label evs = r where
  evsWithIndex = zipWith (\i -> fmap (i,)) [0..] evs
  combine = mergeList evsWithIndex
  nowarn =
    fmapMaybe (\x -> if length x == 1 then Just (head x) else Nothing) combine
  warn = fmapMaybe (\x -> if length x > 1 then Just (head x) else Nothing)
      $ traceEventWith (\xs -> "WARNING: multiple " <> label <> " events triggered: " <> show (fmap fst xs))
      $ fmapMaybe (\x -> if length x > 1 then Just x else Nothing)
      combine
  r = fmap snd $ leftmost [nowarn, warn]

-- | same as leftmostWarn but rpint everything
leftmostWarnWithEverything :: (Reflex t, Show a) => String -> [Event t a] -> Event t a
leftmostWarnWithEverything label evs = r where
  evsWithIndex = zipWith (\i -> fmap (i,)) [0..] evs
  combine = mergeList evsWithIndex
  nowarn =
    fmapMaybe (\x -> if length x == 1 then Just (head x) else Nothing) combine
  warn = fmapMaybe (\x -> if length x > 1 then Just (head x) else Nothing)
      $ traceEventWith (\xs -> "WARNING: multiple " <> label <> " events triggered: " <> show xs)
      $ fmapMaybe (\x -> if length x > 1 then Just x else Nothing)
      combine
  r = fmap snd $ leftmost [nowarn, warn]


-- | same as align but only returns left event if both events fire
-- prints a warning if both events fire
alignWarn
  :: (Reflex t) => String -> Event t a -> Event t b -> Event t (Either a b)
alignWarn label ev1 ev2 =
  leftmostWarn label [Left <$> ev1, Right <$> ev2]


-- | same as align but returns an either and asserts if both events fire at once
alignAssert :: (Reflex t) => String -> Event t a -> Event t b -> Event t (Either a b)
alignAssert label = alignEventWithMaybe alignfn where
  alignfn (This a) = Just $ Left a
  alignfn (That b) = Just $ Right b
  alignfn _        = error $ "both events fired when aligning " <> label

foldDynMergeWith
  :: (Reflex t, MonadHold t m, MonadFix m)
  => b -- ^ initial value of dynamic
  -> [Event t (b -> b)]  -- ^ list of events producing a reducing method
  -> m (Dynamic t b)  -- ^ final output after all folding methods applied
foldDynMergeWith acc = foldDyn ($) acc . mergeWith (.)

foldDynMerge
  :: (Reflex t, MonadHold t m, MonadFix m)
  => (a -> b -> b) -- ^ folding method
  -> b -- ^ initial value of dynamic
  -> [Event t a] -- ^ list of events
  -> m (Dynamic t b) -- ^ final output
foldDynMerge f acc evs = foldDynMergeWith acc (f <<$>> evs)

fanDSum
  :: forall t k
   . (Reflex t, DM.GCompare k)
  => Event t (DS.DSum k Identity)
  -> EventSelector t k
fanDSum ds = fan $ DM.fromAscList . (: []) <$> ds

-- TODO test
pushAlwaysDyn
  :: (Reflex t, MonadHold t m, MonadFix m)
  => (a -> PushM t b)
  -> Dynamic t a
  -> m (Dynamic t b)
pushAlwaysDyn f da = do
  da0 <- sample . current $ da
  buildDynamic (f da0) $ pushAlways f (updated da)


selectNext :: [a] -> Maybe a
selectNext []      = Nothing
selectNext (x : _) = Just x
selectRest :: [a] -> Maybe [a]
selectRest []       = Nothing
selectRest (_ : []) = Nothing
selectRest (_ : xs) = Just xs

-- | delays an event by 1 tick
delayEvent
  :: forall t m a
   . (Adjustable t m)
  => Event t a
  -> m (Event t a)
delayEvent ev = do
  (_, evDelayed) <- runWithReplace (return ()) (fmap return ev)
  return evDelayed

-- | This takes two possibly simultaneous events to and sequences them to fire on different frames.
-- If both events fire at the same time, this functions returns an event with the second event's results that fires one frame after the first event fires.
sequenceEvents
  :: forall t m a b
   . (Adjustable t m, MonadFix m)
  => Event t a
  -> Event t b
  -> m (Event t b)
sequenceEvents ev1 ev2 = mdo
  let makeEv2Delayed :: m (Event t b)
      makeEv2Delayed = do
        let
          -- filters for when BOTH ev1 and ev2 triggered in the previous frame
            fmapfn = \case
              These _ v2 -> Just v2
              _           -> Nothing
            delayed = fmapMaybe fmapfn redo
        -- if ev1 does not trigger, delay does not trigger and this gives ev2
        -- if ev1 did trigger, and ev2 did not, this gives ev2
        -- if ev1 and ev2 both triggered, this gives previous value of evl2
        -- note that it's possible for ev1 or ev2 to trigger in the second frame for outside reasons
        -- if this is the case, you really should not use this function
        return $ leftmost [delayed, difference ev2 ev1]
  (ev2Delayed, redo) <- runWithReplace
    makeEv2Delayed
    (alignEventWithMaybe (Just . return) ev1 ev2)
  return ev2Delayed

-- | Creates an output event that fires once for each input in the list.
-- Each output event runs in a different consecutive frame.
-- If an output event triggers the input event, they get appended to the end of the list of events to be triggered
stepEvents
  :: forall t m a
   . (Adjustable t m, MonadFix m)
  => Event t [a]
  -> m (Event t a)
stepEvents evin = mdo
  let
    -- if input event fires in subsequent ticks, append to end
    -- obviously, be mindful of infinite loops
      evin' :: Event t [a]
      evin' = mergeWith (\rev' ev' -> rev' <> ev') [rev, evin]
      next  = fmapMaybe selectNext evin'
      rest  = fmapMaybe selectRest evin'

  -- TODO this implementation is better but I can't figure out how to properly wrap request and response types
  --rev <- requestingIdentity (Identity <$> rest)
  --requestingIdentity (Identity <$> next)

  (_, rev) <- runWithReplace (return ()) (return <$> rest)
  return next

-- | Same as stepEvents but collects results for each event firing.
stepEventsAndCollectOutput
  :: forall t m a b
   . (Adjustable t m, MonadHold t m, MonadFix m)
  => Event t [a] -- ^ event to repeat
  -> Event t b -- ^ event to collect results from, only collects if event fires
  -> m (Event t a, Event t [b]) -- ^ (repeated event, collected results once event is done repeating)
stepEventsAndCollectOutput evin collectEv = mdo
  let
    -- if input event fires in subsequent ticks, append to end
    -- obviously, be mindful of infinite loops
    evin' :: Event t [a]
    evin' = mergeWith (\rev' ev' -> rev' <> ev') [rev, evin]
    next  = fmapMaybe selectNext evin'
    rest  = fmapMaybe selectRest evin'
    -- nothing left, this means we fired the last event
    stop  = fmapMaybe
      (\x -> if isNothing (selectRest x) then Just () else Nothing)
      evin'
    collected = tagPromptlyDyn (reverse <$> collector) stop

    -- collect events in reverse order
    -- reset when given the signal
    foldfn :: These Bool b -> [b] -> [b]
    foldfn (This True    ) _  = []
    foldfn (That b       ) bs = b : bs
    foldfn (These True  b) _  = [b]
    foldfn (These False b) bs = b : bs
    foldfn _               bs = bs

  -- we use the trick 'tag (current resetState) evin''
  -- which causes it to use resetState from previous iterations.
  collector <- foldDyn
    foldfn
    []
    (alignEventWithMaybe Just (tag (current resetState) evin') collectEv)

  resetState <- foldDyn
    const
    True
    (leftmost [const True <$> stop, const False <$> evin'])

  (_, rev) <- runWithReplace (return ()) (return <$> rest)
  return (next, collected)

-- | Same as stepEventsAndCollectOutput but the collected event fires one frame
-- AFTER the last input event fires
stepEventsAndSequenceCollectOutput
  :: forall t m a b
   . (Adjustable t m, MonadHold t m, MonadFix m)
  => Event t [a] -- ^ event to step
  -> Event t b -- ^ event to collect results from, only collects if event fires
  -> m (Event t a, Event t [b]) -- ^ (repeated event, collected results once event is done repeating)
stepEventsAndSequenceCollectOutput evin collectEv = mdo
  let
    -- if input event fires in subsequent ticks, append to end
    -- obviously, be mindful of infinite loops
    evin' :: Event t [a]
    evin' = mergeWith (\rev' ev' -> rev' <> ev') [rev, evin]
    next  = fmapMaybe selectNext evin'
    rest  = fmapMaybe selectRest evin'
    -- nothing left, this means we fired the last event
    stop  = fmapMaybe
      (\x -> if isNothing (selectRest x) then Just () else Nothing)
      evin'
    collected = tag (current (reverse <$> collector)) stop

    -- collect events in reverse order
    -- reset when given the signal
    foldfn :: These Bool b -> [b] -> [b]
    foldfn (This True    ) _  = []
    foldfn (That b       ) bs = b : bs
    foldfn (These True  b) _  = [b]
    foldfn (These False b) bs = b : bs
    foldfn _               bs = bs

  -- we use the trick 'tag (current resetState) evin''
  -- which causes it to use resetState from previous iterations.
  collector <- foldDyn
    foldfn
    []
    (alignEventWithMaybe Just (updated resetState) collectEv)

  resetState <- foldDyn
    const
    True
    (leftmost [const True <$> stop, const False <$> evin'])

  (_, rev) <- runWithReplace (return ()) (return <$> rest)
  return (next, collected)



switchHoldPair :: (Reflex t, MonadHold t m) => Event t a -> Event t b -> Event t (Event t a, Event t b) -> m (Event t a, Event t b)
switchHoldPair eva evb evin = fmap fanThese $ switchHold (align eva evb) $ fmap (uncurry align) evin

switchHoldTriple :: forall t m a b c. (Reflex t, MonadHold t m) => Event t a -> Event t b -> Event t c -> Event t (Event t a, Event t b, Event t c) -> m (Event t a, Event t b, Event t c)
switchHoldTriple eva evb evc evin = r where
  evinAligned :: Event t (Event t (These a (These b c)))
  evinAligned = fmap (\(eva', evb', evc') -> align eva' (align evb' evc')) evin
  evabc = align eva (align evb evc)
  switched :: m (Event t (These a (These b c)))
  switched = switchHold evabc evinAligned
  fanned1 :: m (Event t a, Event t (These b c))
  fanned1 = fmap fanThese switched
  fanned2 = fmap (\(a,bc) -> (a, fanThese bc)) fanned1
  r = fmap (\(a, (b,c)) -> (a,b,c)) fanned2


-- | produces an event that will fire when the following sequence of conditions happens or happens simultaneously
-- evA fires
-- evB fires
-- 
-- the state is reset after this event fires and the sequence must occur again for the event to fire again
waitForSecondAfterFirst :: (Reflex t, MonadFix m, MonadHold t m) => Event t a -> Event t b -> m (Event t (a, b))
waitForSecondAfterFirst eva evb = mdo
  -- reset state of a firing each time b fires after a fires or at the same time
  aDyn <- holdDyn Nothing $ leftmost [evabsimul $> Nothing, fmap Just eva, evb $> Nothing]
  let 
    -- always fire if both events fire at the same time
    evabsimul = simultaneous eva evb 
    -- only fire when b fires if a fired before
    evbaftera = fmapMaybe (\(ma,b) -> maybe Nothing (Just . (,b)) ma) (attach (current aDyn) evb)
  return $ leftmost [evabsimul, evbaftera]