lens-witherable (empty) → 0.1.0.0
raw patch · 6 files changed
+320/−0 lines, 6 filesdep +basedep +witherable
Dependencies added: base, witherable
Files
- CHANGELOG.md +5/−0
- LICENSE +20/−0
- README.md +125/−0
- lens-witherable.cabal +27/−0
- src/Witherable/Lens.hs +102/−0
- src/Witherable/Lens/Withering.hs +41/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for lens-witherable++## 0.1.0.0 -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2021-2022 Carl Howells++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,125 @@+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++```haskell+withered+ :: (Applicative f, Witherable t)+ => (a -> Withering f b) -> t a -> f (t b)+```++One tool for working with the types is++```haskell+mapMaybeOf+ :: ((a -> Withering Identity b) -> s -> Identity t)+ -> (a -> Maybe b) -> s -> t+```++These can be used like such++```haskell+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++```haskell+filterOf+ :: ((a -> Withering Identity a) -> s -> Identity s)+ -> (a -> Bool) -> s -> s+```++```haskell+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++```haskell+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`.++```haskell+unwithered :: Functor f => (a -> f b) -> a -> Withering f b++guarded+ :: Applicative f+ => (a -> Bool) -> (a -> Withering f b)+ -> a -> Withering f b+```++```haskell+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++```haskell+rewithered+ :: (Applicative f, Witherable t)+ => (a -> Withering f b) -> t a -> Withering f (t b)+```++```haskell+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.
+ lens-witherable.cabal view
@@ -0,0 +1,27 @@+cabal-version: 2.4+name: lens-witherable+version: 0.1.0.0+synopsis: lens-compatible tools for working with witherable+copyright: Copyright (C) 2021-2022 Carl Howells+license: MIT+license-file: LICENSE+author: Carl Howells+maintainer: chowells79@gmail.com+category: Data+homepage: https://github.com/chowells79/lens-witherable+bug-reports: https://github.com/chowells79/lens-witherable/issues+description:+ Provides tools for integrating the witherable package with lens+ combinators. See README.md for more details.++extra-source-files: CHANGELOG.md,+ README.md++library+ default-language: Haskell2010+ exposed-modules: Witherable.Lens,+ Witherable.Lens.Withering++ build-depends: base >=4.9 && <5,+ witherable >= 0.4 && < 0.5+ hs-source-dirs: src
+ src/Witherable/Lens.hs view
@@ -0,0 +1,102 @@+{-|+Module : Witherable.Lens+Description : Tools for using the Witherable interface with lens+Copyright : (c) Carl Howells, 2021+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. The major that instead of merely removing non-matching+-- targets from the traversal, it removes those targets (and their+-- parents up to the next 'withered' combinator) from the data+-- structure entirely.+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 (guarding p)+ where+ guarding p a+ | p a = pure a+ | otherwise = empty+infix 2 `filterOf`++-- | Transform and filter elements matched by a specific 'Withering'+-- context, a la 'Data.Maybe.mapMaybe'.+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`
+ src/Witherable/Lens/Withering.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE StandaloneDeriving, UndecidableInstances #-}++{-|+Module : Witherable.Lens.Withering+Description : MaybeT replacement type+Copyright : (c) Carl Howells, 2021+License : MIT+Maintainer : chowells79@gmail.com++This module contains a replacement for @MaybeT@ intended for use in+lens-like contexts. The important difference from @MaybeT@ is that+'Withering' drops the short-circuiting behavior that requires 'Monad'+constraints.+-}+module Witherable.Lens.Withering (Withering(..), empty) where++import Control.Applicative (liftA2)++-- | A replacement for @MaybeT@ with no short-circuiting+-- behavior. This allows its 'Applicative' instance to not require @f@+-- to be an instance of 'Monad'.+newtype Withering f a = Withering { runWithering :: f (Maybe a) }++deriving instance Eq (f (Maybe a)) => Eq (Withering f a)+deriving instance Ord (f (Maybe a)) => Ord (Withering f a)+deriving instance Show (f (Maybe a)) => Show (Withering f a)++instance Functor f => Functor (Withering f) where+ fmap f (Withering x) = Withering (fmap (fmap f) x)++instance Applicative f => Applicative (Withering f) where+ pure x = Withering (pure (Just x))+ Withering f <*> Withering x = Withering (liftA2 (<*>) f x)++-- | A 'Withering' value wrapping 'Nothing'. This cannot be part of an+-- 'Control.Applicative.Alternative' instance for 'Withering' because+-- it needs to be available with only an 'Applicative' constraint on+-- @f@, and any lawful 'Control.Applicative.Alternative' instance+-- would require more structure than that.+empty :: Applicative f => Withering f a+empty = Withering (pure Nothing)