lens-witherable-0.1.1.0: src/Witherable/Lens.hs
{-|
Module : Witherable.Lens
Description : Tools for using the Witherable interface with lens
Copyright : (c) Carl Howells, 2021-2022
License : MIT
Maintainer : chowells79@gmail.com
-}
module Witherable.Lens where
import Data.Functor.Identity (Identity(runIdentity))
import Witherable (Witherable(wither))
import Witherable.Lens.Withering
-- | A variant on 'traverse' that allows the targets to be filtered
-- out of the 'Witherable' structure. Note that this introduces a
-- change in types down the lens composition chain, which means that
-- it is not a a valid optic at all. The use of 'Withering' in the
-- changed type also means that standard lens combinators don't fit
--
-- To address these issues, you can use 'unwithered' to strip the
-- 'Withering' type back out. This allows the composed optic to be
-- used with standard combinators from lens. In addition, the sequence
-- @'withered' . 'unwithered'@ will act like a type-restricted version
-- of 'traverse' for all lawful instances of 'Witherable'.
--
-- In some sense, this is a @catch@-like combinator. This marks the
-- point where removing elements stops propagating and actually
-- modifies the structure being focused.
withered
:: (Applicative f, Witherable t)
=> (a -> Withering f b) -> t a -> f (t b)
withered f = wither (runWithering . f)
-- | Restore types in a lens composition chain that has had
-- 'Withering' introduced. Makes no changes to what elements are
-- focused on.
unwithered :: Functor f => (a -> f b) -> a -> Withering f b
unwithered f s = Withering (fmap Just (f s))
-- | A variant of withered for when you're already working in a
-- Withering chain and want to change what structure elements are
-- being removed from.
--
-- @'rewithered' = 'unwithered' . 'withered'@
rewithered
:: (Applicative f, Witherable t)
=> (a -> Withering f b) -> t a -> Withering f (t b)
rewithered = unwithered . withered
-- | The trivial optic in a Withering chain that removes everything.
--
-- The arguments are unused.
decayed :: Applicative f => pafb -> s -> Withering f t
decayed _ _ = empty
-- | Remove elements from the current 'Withering' context if they
-- don't match the predicate. This is similar in concept to @filtered@
-- from lens but instead of merely removing non-matching
-- targets from the traversal, it removes those targets and their
-- parents up to the next 'withered' combinator.
guarded
:: Applicative f
=> (a -> Bool) -> (a -> Withering f b)
-> a -> Withering f b
guarded p f a
| p a = f a
| otherwise = empty
-- | Remove elements matched by a specific 'Withering' context if they
-- don't match a predicate.
filterOf
:: ((a -> Withering Identity a) -> s -> Identity s)
-> (a -> Bool) -> s -> s
filterOf w p = runIdentity . w toWithering
where
toWithering a
| p a = pure a
| otherwise = empty
infix 2 `filterOf`
-- | Remove elements matched by a specific 'Withering' context if they
-- don't match a predicate returning a result in an arbitrary
-- Applicative context.
filterOfA
:: Applicative f
=> ((a -> Withering f a) -> s -> f s)
-> (a -> f Bool) -> s -> f s
filterOfA w p = w toWitheringA
where
toWitheringA a = Withering $ (\x -> if x then Just a else Nothing) <$> p a
infix 2 `filterOfA`
-- | Transform and filter elements matched by a specific 'Withering'
-- context, a la 'Data.Maybe.mapMaybe'. See 'witherOf' for a more
-- flexible version that works within arbitrary 'Applicative' effects.
mapMaybeOf
:: ((a -> Withering Identity b) -> s -> Identity t)
-> (a -> Maybe b) -> s -> t
mapMaybeOf w p = runIdentity . w (Withering . pure . p)
infix 2 `mapMaybeOf`
-- | Transform and effectfully filter elements matched by a specific
-- 'Withering' context, a la 'wither'.
witherOf
:: ((a -> Withering f b) -> s -> f t)
-> (a -> f (Maybe b)) -> s -> f t
witherOf w p = w (Withering . p)
infix 2 `witherOf`