distributors-0.4.0.0: src/Control/Lens/Wither.hs
{- |
Module : Control.Lens.Wither
Description : withers
Copyright : (C) 2026 - Eitan Chatav
License : BSD-style (see the file LICENSE)
Maintainer : Eitan Chatav <eitan.chatav@gmail.com>
Stability : provisional
Portability : non-portable
See Chris Penner,
[Composable Filters Using Witherable Optics](https://chrispenner.ca/posts/witherable-optics)
-}
module Control.Lens.Wither
( -- * Wither
Wither
, AWither
-- * Combinators
, cloneWither
, withered
, filtraversed
, filterOf
-- * Witheroid
, Witheroid
, witherPrism
-- * Altar
, Altar (..)
) where
import Control.Applicative
import Control.Lens
import Control.Lens.Internal.Context
import Data.Profunctor.Rep
import Data.Profunctor.Sieve
import Prelude hiding (filter)
import Witherable
{- | `Wither`s extends `Control.Lens.Traversal.Traversal`s by filtering.
Every one of the following is a `Wither`.
* `Control.Lens.Iso.Iso`
* `Control.Lens.Lens.Lens`
* `Control.Lens.Prism.Prism`
* `Control.Lens.Traversal.Traversal`
* `Witheroid`
-}
type Wither s t a b = forall f. Alternative f => (a -> f b) -> s -> f t
{- | `AWither` is monomorphically a `Wither`. -}
type AWither s t a b = (a -> Altar a b b) -> s -> Altar a b t
{- | `Witheroid`s generalize `Wither`s.
Every `Control.Lens.Prism.Prism` is a `Witheroid`.
-}
type Witheroid s t a b = forall p f.
(Choice p, Alternative f)
=> p a (f b) -> p s (f t)
{- | Clone `AWither` so that you can reuse the same
monomorphically typed `Wither` for different purposes.
-}
cloneWither :: AWither s t a b -> Wither s t a b
cloneWither w f = (\g z -> runAltar z g) f . w sell
{- | Construct a `Wither` for a `Witherable`. -}
withered :: Witherable t => Wither (t a) (t b) a b
withered f = wither (optional . f)
{- |
prop> withered = filtraversed
-}
filtraversed :: (Filterable t, Traversable t) => Wither (t a) (t b) a b
filtraversed f = fmap catMaybes . traverse (optional . f)
{- | Filter a traversed structure based on a predicate. -}
filterOf :: Alternative m => Wither s t a a -> (a -> Bool) -> s -> m t
filterOf w p s = w guardingp s where
guardingp a
| p a = pure a
| otherwise = empty
{- |
`Control.Lens.Prism.Prism`s already capture the idea of success and failure,
but they simply skip the traversal if the prism doesn't match.
Lift prisms into withers such that they'll fail in a way that wither can catch.
-}
witherPrism :: APrism s t a b -> Witheroid s t a b
witherPrism prsm =
withPrism prsm $ \embed match ->
dimap match (either (const empty) (fmap embed)) . right'
{- | This is used to characterize `Wither`s. -}
newtype Altar a b t = Altar
{ runAltar :: forall f. Alternative f => (a -> f b) -> f t }
instance Functor (Altar a b) where
fmap f (Altar k) = Altar (fmap f . k)
instance Applicative (Altar a b) where
pure a = Altar $ const (pure a)
Altar mf <*> Altar ma = Altar $ liftA2 (<*>) mf ma
instance Alternative (Altar a b) where
empty = Altar $ const empty
Altar mx <|> Altar my = Altar $ liftA2 (<|>) mx my
instance Sellable (->) Altar where
sell
= cotabulate
$ \w -> Altar
$ tabulate
$ \k -> pure (cosieve k w)