@@ -34,6 +34,7 @@ module Reflex.Class
3434 , mergeMap
3535 , mergeList
3636 , mergeWith
37+ , difference
3738 -- ** Breaking up 'Event's
3839 , splitE
3940 , fanEither
@@ -408,8 +409,21 @@ instance (Reflex t, Semigroup a) => Semigroup (Behavior t a) where
408409#endif
409410
410411--TODO: See if there's a better class in the standard libraries already
412+
411413-- | A class for values that combines filtering and mapping using 'Maybe'.
412−class FunctorMaybe f where
414+-- Morally, @'FunctorMaybe' ~ KleisliFunctor 'Maybe'@. Also similar is the
415+-- @Witherable@ typeclass, but it requires @Foldable f@ and @Traverable f@,
416+-- and e.g. 'Event' is instance of neither.
417+--
418+-- A definition of 'fmapMaybe' must satisfy the following laws:
419+--
420+-- [/identity/]
421+-- @'fmapMaybe' 'Just' ≡ 'id'@
422+--
423+-- [/composition/]
424+-- @'fmapMaybe' (f <=< g) ≡ 'fmapMaybe' f . 'fmapMaybe' g@
425+
426+class Functor f => FunctorMaybe f where
413427 -- | Combined mapping and filtering function.
414428 fmapMaybe :: (a -> Maybe b) -> f a -> f b
415429
@@ -640,7 +654,7 @@ switchPromptly ea0 eea = do
640654
641655instance Reflex t => Align (Event t) where
642656 nil = never
643− align ea eb = fmapMaybe dmapToThese $ merge $ DMap.fromList [LeftTag :=> ea, RightTag :=> eb]
657+ align = alignWithMaybe Just
644658
645659-- | Create a new 'Event' that only occurs if the supplied 'Event' occurs and
646660-- the 'Behavior' is true at the time of occurence.
@@ -660,6 +674,7 @@ zipDyn = zipDynWith (,)
660674
661675-- | Combine two 'Dynamic's with a combining function. The result will change
662676-- whenever either (or both) input 'Dynamic' changes.
677+-- More efficient than liftA2.
663678zipDynWith :: Reflex t => (a -> b -> c) -> Dynamic t a -> Dynamic t b -> Dynamic t c
664679zipDynWith f da db =
665680 let eab = align (updated da) (updated db)
@@ -703,6 +718,21 @@ distributeListOverDyn = distributeListOverDynWith id
703718distributeListOverDynWith :: Reflex t => ([a] -> b) -> [Dynamic t a] -> Dynamic t b
704719distributeListOverDynWith f = fmap (f . map (\(Const2 _ :=> Identity v) -> v) . DMap.toList) . distributeDMapOverDynPure . DMap.fromList . map (\(k, v) -> Const2 k :=> v) . zip [0 :: Int ..]
705720
721+-- | Create a new 'Event' that occurs when the first supplied 'Event' occurs
722+-- unless the second supplied 'Event' occurs simultaneously.
723+difference :: Reflex t => Event t a -> Event t b -> Event t a
724+difference = alignWithMaybe $ \case
725+ This a -> Just a
726+ _ -> Nothing
727+
728+-- (intentially not exported, for now)
729+alignWithMaybe
730+ :: Reflex t => (These a b -> Maybe c) -> Event t a -> Event t b -> Event t c
731+alignWithMaybe f ea eb =
732+ fmapMaybe (f <=< dmapToThese)
733+ $ merge
734+ $ DMap.fromList [LeftTag :=> ea, RightTag :=> eb]
735+
706736
707737--------------------------------------------------------------------------------
708738-- Accumulator