lens-witherable: lens-compatible tools for working with witherable

[ data, library, mit ] [ Propose Tags ]

Provides tools for integrating the witherable package with lens combinators. See README.md for more details.


[Skip to Readme]

Flags

Automatic Flags
NameDescriptionDefault
top-level-witherable

Import Witherable instead of Data.Witherable

Enabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.1.0.1, 0.1.0.2, 0.1.1.0, 0.2.0.0, 0.2.0.1, 0.2.0.2
Change log CHANGELOG.md
Dependencies base (>=4.10 && <5), containers (>=0.5 && <0.8), hashable (>=1.2.7.0 && <1.5), transformers (>=0.5.2.0 && <0.7), unordered-containers (>=0.2.12.0 && <0.3), witherable (<0.5) [details]
License MIT
Copyright Copyright (C) 2021-2024 Carl Howells
Author Carl Howells
Maintainer chowells79@gmail.com
Category Data
Home page https://github.com/chowells79/lens-witherable
Bug tracker https://github.com/chowells79/lens-witherable/issues
Uploaded by CarlHowells at 2024-03-11T05:40:28Z
Distributions NixOS:0.2.0.2
Downloads 206 total (43 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for lens-witherable-0.2.0.2

[back to package description]

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 Traversals 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.