lens-compatible tools for working with witherable.
Provides tools for integrating the witherable package with lens combinators. See README.md for more details.
Integrate witherable and lens.
Based on the ideas from https://chrispenner.ca/posts/witherable-optics but with a more combinator-based approach to restore full compatibility with lens combinators like set
and over
. Additionally, this approach gets rid of Maybe
wrappers in results that are always Just
.
This library is based on a variant of Traversal
s with the type forall f. Applicative f => ((a-> Withering f b) -> s -> f t)
. Note that this type is absolutely not an optic due to the Withering
wrapper, but it's really close. It composes on either side with lenses and traversals with (.)
and that composition maintains the filtering type. No type alias is provided for this type because type aliases containing a forall
cause problems with subsumption.
The core tool for creating values with these types is
withered
:: (Applicative f, Witherable t)
=> (a -> Withering f b) -> t a -> f (t b)
One tool for working with the types is
mapMaybeOf
:: ((a -> Withering Identity b) -> s -> Identity t)
-> (a -> Maybe b) -> s -> t
These can be used like such
ghci> let z = M.fromList [('a', ["1", "2", "3"]), ('b', ["4", "Alpaca", "6"])]
ghci> z & mapMaybeOf (traverse . withered) (readMaybe :: String -> Maybe Int)
fromList [('a',[1,2,3]),('b',[4,6])]
ghci> z & mapMaybeOf (withered . traverse) (readMaybe :: String -> Maybe Int)
fromList [('a',[1,2,3])]
As those results demonstrate, the location of the withered
combinator controls where the pruning stops. It functions as a kind of catch combinator in this sense.
Of course, filterOf
is provided as well
filterOf
:: ((a -> Withering Identity a) -> s -> Identity s)
-> (a -> Bool) -> s -> s
ghci> [[1, 2, 3], [4, 5, 6]] & filterOf (traverse . withered) even
[[2],[4,6]]
Traversals that might fail can be combined with the decayed
combinator to cause them to completely remove a value that they otherwise would ignore
ghci> [('a', Right 1), ('b', Left 2), ('c', Left 3)] & filterOf (withered . _2 . _Left) even
[('a',Right 1),('b',Left 2)]
ghci> [('a', Right 1), ('b', Left 2), ('c', Left 3)] & filterOf (withered . _2 . (_Left `failing` decayed)) even
[('b',Left 2)]
In the first case, the 'a'
value is returned because the filter doesn't find any non-even values under the Right
value when it's looking under the _Left
prism. In the second case, the failure of the _Left
prism results in falling back on decayed
to remove the branch.
Compatibility with normal lens operations can be restored with unwithered
. This can be especially useful when combined with guarded
, which acts somewhat like lens's filtered
but it prunes the structure up to the next withered
.
unwithered :: Functor f => (a -> f b) -> a -> Withering f b
guarded
:: Applicative f
=> (a -> Bool) -> (a -> Withering f b)
-> a -> Withering f b
ghci> [[1,2],[],[1,3,9],[],[],[6,4,7],[]] & withered . guarded (not . null) . unwithered . traverse +~ 10
[[11,12],[11,13,19],[16,14,17]]
Note that pruning out the empty lists happens in-line with the modification of the other elements. An additional combinator is provided combining withered
and unwithered
for when you want to change the pruning depth
rewithered
:: (Applicative f, Witherable t)
=> (a -> Withering f b) -> t a -> Withering f (t b)
ghci> [[1,2],[],[1,3,9],[],[],[6,4,7],[]] & (withered . guarded (not . null) . rewithered . guarded even . unwithered) +~ 10
[[12],[],[16,14]]
In that example, the first guarded
strips out null lists in the input. The rewithered
descends into the sublists and sets each of them as the catch point for later pruning. The second guarded
strips out all non-even entries in each list. The unwithered
restores compatibility with lens combinators like (+~)
, which is used to modify every remaining focused value. Note the presence of the []
in the output list. The guarded
combinator violates the lens laws in the same manner as filtered
, where behavior might change with refactoring. This doesn't mean it's dangerous to use, merely that you have to pay attention to changes in behavior when refactoring chains involving it.