summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChrisPenner <>2017-08-12 17:43:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2017-08-12 17:43:00 (GMT)
commitb0403f0c7b25b157ec41c2138fb45820e598136e (patch)
treec3e9bbbeb3d4be9cf4c7285ec15548fd7951158d
parent8b56392b919a5fb37c2f4911a92749377bc53f0e (diff)
version 0.1.0.40.1.0.4
-rw-r--r--README.md104
-rw-r--r--selections.cabal7
-rw-r--r--src/Control/Lens/Selection.hs41
-rw-r--r--src/Data/Functor/Selection.hs94
4 files changed, 107 insertions, 139 deletions
diff --git a/README.md b/README.md
index 0df75b7..a95e133 100644
--- a/README.md
+++ b/README.md
@@ -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))