**diff options**

author | ChrisPenner <> | 2017-08-12 17:43:00 (GMT) |
---|---|---|

committer | hdiff <hdiff@hdiff.luite.com> | 2017-08-12 17:43:00 (GMT) |

commit | b0403f0c7b25b157ec41c2138fb45820e598136e (patch) | |

tree | c3e9bbbeb3d4be9cf4c7285ec15548fd7951158d | |

parent | 8b56392b919a5fb37c2f4911a92749377bc53f0e (diff) |

version 0.1.0.40.1.0.4

-rw-r--r-- | README.md | 104 | ||||

-rw-r--r-- | selections.cabal | 7 | ||||

-rw-r--r-- | src/Control/Lens/Selection.hs | 41 | ||||

-rw-r--r-- | src/Data/Functor/Selection.hs | 94 |

4 files changed, 107 insertions, 139 deletions

@@ -25,99 +25,35 @@ you to: - Perform monad computations over selected values if your functor is a Monad - Extract all unselected or selected elements to a list - Deselect and return to your original functor using `unify` +- Traverse or fold over selections using `Control.Lens` -Lenses and traversals coming eventually! - -Technically you could use `Selection` as a monad-transformer, but it's a bit -clunky and you'd probably be better off with -[`EitherT`](https://hackage.haskell.org/package/either-4.4.1.1/docs/Control-Monad-Trans-Either.html). -Fun fact, `Selection` is isomorphic to `EitherT`, but the semantics are quite -different and they're suited to different purposes. - -## Examples - -We'll start off using a simple list as our underlying functor. - -You may find it useful to throw in some [Type -Applications](https://ghc.haskell.org/trac/ghc/wiki/TypeApplication) to help -disambiguate type for the compiler. This typically isn't an issue in compiled code, -but the interpreter can get confused from time to time. +Here's how it looks, more practical examples are available [here](./src/Examples). ```haskell -{-# language TypeApplications #-} -import Data.Functor.Selection - --- This combinator is super handy for chaining selections along -(&) :: a -> (a -> c) -> c -(&) = flip ($) - -xs :: [Int] xs = [1..6] +λ> newSelection xs & select even & mapSelected (+100) & bimap (("Odd: " ++) . show) (("Even: " ++) . show) & forgetSelection +["Odd: 1","Even: 102","Odd: 3","Even: 104","Odd: 5","Even: 106"] ``` -Let's select just the even numbers and see what we get! - -```haskell -evens :: Selection [] Int Int -evens = newSelection xs & select even --- Selection {runSelection = [Left 1,Right 2,Left 3,Right 4,Left 5,Right 6]} -``` - -Cool, we can see that the underlying representation consists of our original -functor, except it uses Either to show which elements are selected. Let's -multiply our odd elements by two with `mapUnselected` - -```haskell -byTwo :: Selection [] Int Int -byTwo = evens & mapUnselected (*2) --- Selection {runSelection = [Left 2,Right 2,Left 6,Right 4,Left 10,Right 6]} -``` - -Notice that even though the numbers became even as a result of the transformation -the same elements remain selected. If you wanted to you could run 'select even' -again to include the new elements. - -Let's exclude anything greater than 5 from our selection, then get the numbers -that remain selected as a list - -```haskell -excluded :: [Int] -excluded = byTwo & exclude (>5) & getSelected --- [2, 4] -``` -Nice! Looks like it worked! Notice how the order of elements remained -the same through the whole thing! - -The types of the selected and unselected elements are allowed to diverge! -So long as they line up when we decide to get the results then it's all good! -We can use `unify :: Selectable s f => (b -> c) -> (a -> c) -> s b a -> f c` to -help us out with that. - -Let's try it out! +Technically you could use `Selection` as a monad-transformer, but it's a bit +clunky and you'd probably be better off with +[`EitherT`](https://hackage.haskell.org/package/either-4.4.1.1/docs/Control-Monad-Trans-Either.html). -```haskell -diverged :: Selection [] String Int -diverged = newSelection @(Selection []) [1..6] & select even & mapUnselected show --- Selection {runSelection = [Left "1",Right 2,Left "3",Right 4,Left "5",Right 6]} - -unified :: [Int] -unified = diverged & mapUnselected ("100"++) & unify read id -[1001,2,1003,4,1005,6] -``` +Fun fact, `Selection` is isomorphic to `EitherT`, but the semantics are quite +different and they're suited to different purposes. -`[]` is a great functor to try out because it has Applicative and Monad instances -defined, which both act pretty much as you'd expect, but they only consider selected -values! +## When Should/Shouldn't I Use Selections? -```haskell -evens :: Selection [] Int Int -evens = newSelection [1..6] & select even +You can use selections whenever you've got a bunch of things and you want to operate over just a few of them at a time. +You can do everything that selections provides by combining a bunch of predicates with fmap, but it gets messy really +quick; selections provides a clean interface for this sort of operation. -plus10 :: Selection [] Int Int -plus10 = do - x <- evens - newSelection [x + 10, x + 100] --- Selection {runSelection = [Left 1,Right 12,Right 102,Left 3,Right 14,Right 104,Left 5,Right 16,Right 106]} -``` +You shouldn't use selections when you're looking for a monadic interface for this sort of thing, selections works +at the value level and typically you want to chain selection commands using `(.)` or `(&)`, it technically can +be used as a monad transformer if your underlying functor is also a monad, but at that point you may wish to check +out [`EitherT`](https://hackage.haskell.org/package/either-4.4.1.1/docs/Control-Monad-Trans-Either.html) instead. +## Examples +Check out the [Accounts tutorial](./src/Examples/Accounts.lhs) first to get your bearings, it's a literate haskell file +so you can load it up in ghci if you like! After that continue to the [Lens tutorial](./src/Examples/Accounts.lhs). diff --git a/selections.cabal b/selections.cabal index bd1fa84..a8d236e 100644 --- a/selections.cabal +++ b/selections.cabal @@ -1,7 +1,7 @@ name: selections -version: 0.1.0.3 +version: 0.1.0.4 synopsis: Combinators for operating with selections over an underlying functor --- description: +description: See the [README on github](https://github.com/ChrisPenner/selections#readme) for tutorials! homepage: https://github.com/ChrisPenner/selections#readme license: BSD3 license-file: LICENSE @@ -16,9 +16,12 @@ cabal-version: >=1.10 library hs-source-dirs: src exposed-modules: Data.Functor.Selection + , Control.Lens.Selection build-depends: base >= 4.7 && < 5 , comonad , bifunctors + , profunctors + , lens default-language: Haskell2010 source-repository head diff --git a/src/Control/Lens/Selection.hs b/src/Control/Lens/Selection.hs new file mode 100644 index 0000000..1ffd807 --- /dev/null +++ b/src/Control/Lens/Selection.hs @@ -0,0 +1,41 @@ +{-# language ScopedTypeVariables #-} +{-# language RankNTypes #-} +module Control.Lens.Selection + ( selected + , unselected + , inverting + , unwrapping + ) where + +import Data.Profunctor (Profunctor(..)) +import Data.Functor.Selection + +type Traversal s t a b = forall f. Applicative f => (a -> f b) -> s -> f t +type Traversal' s a = Traversal s s a a +type Iso s t a b = forall p f. (Profunctor p, Functor f) => p a (f b) -> p s (f t) +type Iso' s a = Iso s s a a + +-- | Traversal over selected elements +-- +-- @'selected' = 'traverse'@ +selected :: (Traversable f) => Traversal' (Selection f b a) a +selected = traverse + +-- | Traversal over unselected elements +-- +-- @'unselected' = 'inverting' . 'selected'@ +unselected :: (Traversable f) => Traversal' (Selection f b a) b +unselected = inverting . selected + + +-- | Iso which inverts the current selection +-- +-- @'inverting' = iso 'invertSelection' 'invertSelection'@ +inverting :: (Functor f) => Iso' (Selection f b a) (Selection f a b) +inverting = dimap invertSelection (fmap invertSelection) + +-- | Iso which exposes the underlying functor representation +-- +-- @'unwrapping' = iso 'unwrapSelection' 'wrapSelection'@ +unwrapping :: (Functor f) => Iso' (Selection f b a) (f (Either b a)) +unwrapping = dimap unwrapSelection (fmap Selection) diff --git a/src/Data/Functor/Selection.hs b/src/Data/Functor/Selection.hs index 5ae8442..84558b0 100644 --- a/src/Data/Functor/Selection.hs +++ b/src/Data/Functor/Selection.hs @@ -1,14 +1,15 @@ -{-# language CPP #-} {-# language FlexibleInstances #-} {-# language DeriveFunctor #-} {-# language DeriveFoldable #-} +{-# language DeriveTraversable #-} {-# language UndecidableInstances #-} {-# language RankNTypes #-} -{-# language FunctionalDependencies #-} {-# language StandaloneDeriving #-} module Data.Functor.Selection ( -- * Selection Selection(..) + , Selection' + , modifySelection -- ** Selecting/Deselecting -- | Most selection combinators require that both the selected and unselected types -- be equal (i.e. Selection f a a); this is necessary since items will switch @@ -33,33 +34,20 @@ module Data.Functor.Selection , selectWithContext ) where -#if __GLASGOW_HASKELL__ < 710 -import Control.Applicative -#endif import Control.Monad (ap) import Control.Comonad (Comonad(..)) import Data.Bifoldable (Bifoldable(..)) import Data.Bifunctor (Bifunctor(..)) import Data.Bitraversable (Bitraversable(..)) +-- | A selection wraps a Functor @f@ and has an unselected type @b@ and a selected type @a@ +newtype Selection f b a = Selection + { -- | Expose the underlying representation of a 'Selection' + unwrapSelection :: f (Either b a) + } deriving (Functor, Foldable, Traversable) --- | You can make any type selectable if it contains a functor of (Either b a) -class Functor f => Selectable s f | s -> f where - -- Modify the underlying representation of a selection - modifySelection :: (f (Either b a) -> f (Either d c)) -> s b a -> s d c - modifySelection f = wrapSelection . f . unwrapSelection - - -- | Lift a functor into the Selectable - wrapSelection :: f (Either b a) -> s b a - - -- | Extract the underlying functor from the Selectable - unwrapSelection :: s b a -> f (Either b a) - --- | The simplest selection type -newtype Selection f b a = Selection { - -- | Expose the underlying representation of a Selection - runSelection :: f (Either b a) - } deriving (Functor, Foldable) +-- | A type alias for selections with the same unselected/selected types +type Selection' f a = Selection f a a deriving instance (Show (f (Either b a))) => Show (Selection f b a) deriving instance (Eq (f (Either b a))) => Eq (Selection f b a) @@ -72,113 +60,113 @@ instance Monad m => Applicative (Selection m b) where instance (Monad m) => Monad (Selection m b) where return = pure Selection m >>= k = - Selection $ m >>= either (return . Left) (runSelection . k) + Selection $ m >>= either (return . Left) (unwrapSelection . k) -- | Bifunctor over unselected ('first') and selected ('second') values instance (Functor f) => Bifunctor (Selection f) where - first f = Selection . fmap (first f) . runSelection + first f = Selection . fmap (first f) . unwrapSelection second = fmap -- | Bifoldable over unselected and selected values respectively instance (Foldable f) => Bifoldable (Selection f) where - bifoldMap l r = foldMap (bifoldMap l r) . runSelection + bifoldMap l r = foldMap (bifoldMap l r) . unwrapSelection -- | Bitraversable over unselected and selected values respectively instance (Traversable f) => Bitraversable (Selection f) where - bitraverse l r = fmap Selection . traverse (bitraverse l r) . runSelection + bitraverse l r = fmap Selection . traverse (bitraverse l r) . unwrapSelection -instance (Functor f) => Selectable (Selection f) f where - -- modifySelection f (Selection s) = Selection $ f s - wrapSelection = Selection - unwrapSelection = runSelection +-- | Modify the underlying representation of a selection +modifySelection :: (Functor f) => (f (Either b a) -> g (Either d c)) -> Selection f b a -> Selection g d c +modifySelection f = Selection . f . unwrapSelection -- | Create a selection from a functor by selecting all values -newSelection :: (Selectable s f) => f a -> s b a -newSelection = wrapSelection . fmap Right +newSelection :: (Functor f) => f a -> Selection f b a +newSelection = Selection . fmap Right -- | Drops selection from your functor returning all values (selected or not). -- -- @'forgetSelection' . 'newSelection' = id@ -- -- @'forgetSelection' = 'unify' id id@ -forgetSelection :: (Selectable s f) => s a a -> f a +forgetSelection :: (Functor f) => Selection f a a -> f a forgetSelection = unify id id -- | Clear the selection then select only items which match a predicate. -- -- @'select' f = 'include' f . 'deselectAll'@ -select :: (Selectable s f) => (a -> Bool) -> s a a -> s a a +select :: (Functor f) => (a -> Bool) -> Selection f a a -> Selection f a a select f = include f . deselectAll + -- | Add items which match a predicate to the current selection -- -- @'include' f . 'select' g = 'select' (\a -> f a || g a)@ -include :: (Selectable s f) => (a -> Bool) -> s a a -> s a a +include :: (Functor f) => (a -> Bool) -> Selection f a a -> Selection f a a include f = modifySelection (fmap (either (choose f) Right)) -- | Remove items which match a predicate to the current selection -- -- @'exclude' f . 'select' g = 'select' (\a -> f a && not (g a))@ -exclude :: (Selectable s f) => (a -> Bool) -> s a a -> s a a +exclude :: (Functor f) => (a -> Bool) -> Selection f a a -> Selection f a a exclude f = modifySelection (fmap (either Left (switch . choose f))) -- | Select all items in the container -- -- @'selectAll' = 'include' (const True)@ -selectAll :: (Selectable s f) => s a a -> s a a +selectAll :: (Functor f) => Selection f a a -> Selection f a a selectAll = include (const True) -- | Deselect all items in the container -- -- @'deselectAll' = 'exclude' (const True)@ -deselectAll :: (Selectable s f) => s a a -> s a a +deselectAll :: (Functor f) => Selection f a a -> Selection f a a deselectAll = exclude (const True) -- | Flip the selection, all selected are now unselected and vice versa. -invertSelection :: (Selectable s f) => s b a -> s a b +invertSelection :: (Functor f) => Selection f b a -> Selection f a b invertSelection = modifySelection (fmap switch) -- | Map over selected values -- --- @'onSelected' = fmap@ -mapSelected :: (Selectable s f) => (a -> c) -> s b a -> s b c -mapSelected f = modifySelection (fmap (second f)) +-- @'mapSelected' = fmap@ +mapSelected :: (Functor f) => (a -> c) -> Selection f b a -> Selection f b c +mapSelected = fmap -- | Map over unselected values -- --- @'onSelected' f = 'modifySelection' (fmap ('first' f))@ -mapUnselected :: (Selectable s f) => (b -> c) -> s b a -> s c a -mapUnselected f = modifySelection (fmap (first f)) +-- @'mapUnselected' = 'first'@ +mapUnselected :: (Functor f) => (b -> c) -> Selection f b a -> Selection f c a +mapUnselected = first -- | Collect all selected values into a list. For more complex operations use -- foldMap. -- -- @'getSelected' = foldMap (:[])@ -getSelected :: (Selectable s f, Foldable f) => s b a -> [a] -getSelected = foldMap (bifoldMap (const []) pure) . unwrapSelection +getSelected :: (Foldable f) => Selection f b a -> [a] +getSelected = foldMap (:[]) -- | Collect all unselected values into a list. For more complex operations use -- operations from Bifoldable. -- -- @'getUnselected' = 'getSelected' . 'invertSelection'@ -getUnselected :: (Selectable s f, Foldable f) => s b a -> [b] -getUnselected = getSelected . invertSelection +getUnselected :: (Foldable f, Functor f) => Selection f b a -> [b] +getUnselected = bifoldMap (:[]) (const []) -- | Unify selected and unselected and forget the selection -- -- @'unify' f g == 'forgetSelection' . 'onUnselected' f . 'onSelected' g@ -unify :: (Selectable s f) => (b -> c) -> (a -> c) -> s b a -> f c +unify :: (Functor f) => (b -> c) -> (a -> c) -> Selection f b a -> f c unify l r = fmap (either l r) . unwrapSelection -- | Perform a natural transformation over the underlying container of a selectable -trans :: (Selectable s f, Selectable t g) => (forall c. f c -> g c) -> s b a -> t b a -trans f = wrapSelection . f . unwrapSelection +trans :: (Functor f) => (forall c. f c -> g c) -> Selection f b a -> Selection g b a +trans f = modifySelection f -- Comonad combinators -- | Select values based on their context within a comonad. This combinator makes -- its selection by running the predicate using extend. -selectWithContext :: (Selectable s w, Comonad w) => (w a -> Bool) -> s a a -> s a a +selectWithContext :: (Comonad w) => (w a -> Bool) -> Selection w a a -> Selection w a a selectWithContext f = modifySelection (extend (choose' extract f) . fmap (either id id)) |