contravariant 1.4 → 1.4.1
raw patch · 7 files changed
+404/−104 lines, 7 filesnew-uploaderPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Data.Functor.Contravariant: instance Data.Functor.Contravariant.Contravariant (Control.Applicative.Const a)
- Data.Functor.Contravariant.Divisible: instance GHC.Base.Monoid m => Data.Functor.Contravariant.Divisible.Divisible (Control.Applicative.Const m)
- Data.Functor.Contravariant.Generic: instance (Data.Functor.Contravariant.Generic.GDeciding q f, Data.Functor.Contravariant.Generic.GDeciding q g) => Data.Functor.Contravariant.Generic.GDeciding q (f GHC.Generics.:*: g)
- Data.Functor.Contravariant.Generic: instance (Data.Functor.Contravariant.Generic.GDeciding q f, Data.Functor.Contravariant.Generic.GDeciding q g) => Data.Functor.Contravariant.Generic.GDeciding q (f GHC.Generics.:+: g)
- Data.Functor.Contravariant.Generic: instance Data.Functor.Contravariant.Generic.GDeciding q f => Data.Functor.Contravariant.Generic.GDeciding q (GHC.Generics.M1 i c f)
+ Data.Functor.Contravariant: instance Data.Functor.Contravariant.Contravariant (Data.Functor.Const.Const a)
+ Data.Functor.Contravariant: instance Data.Semigroup.Semigroup (Data.Functor.Contravariant.Predicate a)
+ Data.Functor.Contravariant: instance GHC.Base.Monoid (Data.Functor.Contravariant.Predicate a)
+ Data.Functor.Contravariant.Divisible: instance Data.Functor.Contravariant.Divisible.Decidable Data.StateVar.SettableStateVar
+ Data.Functor.Contravariant.Divisible: instance Data.Functor.Contravariant.Divisible.Divisible Data.StateVar.SettableStateVar
+ Data.Functor.Contravariant.Divisible: instance GHC.Base.Monoid m => Data.Functor.Contravariant.Divisible.Divisible (Data.Functor.Const.Const m)
+ Data.Functor.Contravariant.Generic: instance forall k (q :: * -> GHC.Types.Constraint) (f :: k -> *) (g :: k -> *). (Data.Functor.Contravariant.Generic.GDeciding q f, Data.Functor.Contravariant.Generic.GDeciding q g) => Data.Functor.Contravariant.Generic.GDeciding q (f GHC.Generics.:*: g)
+ Data.Functor.Contravariant.Generic: instance forall k (q :: * -> GHC.Types.Constraint) (f :: k -> *) (g :: k -> *). (Data.Functor.Contravariant.Generic.GDeciding q f, Data.Functor.Contravariant.Generic.GDeciding q g) => Data.Functor.Contravariant.Generic.GDeciding q (f GHC.Generics.:+: g)
+ Data.Functor.Contravariant.Generic: instance forall k (q :: * -> GHC.Types.Constraint) (f :: k -> *) i (c :: GHC.Generics.Meta). Data.Functor.Contravariant.Generic.GDeciding q f => Data.Functor.Contravariant.Generic.GDeciding q (GHC.Generics.M1 i c f)
Files
- .travis.yml +141/−48
- CHANGELOG.markdown +11/−0
- HLint.hs +2/−1
- README.markdown +17/−0
- contravariant.cabal +17/−5
- src/Data/Functor/Contravariant.hs +44/−6
- src/Data/Functor/Contravariant/Divisible.hs +172/−44
.travis.yml view
@@ -1,62 +1,155 @@-# NB: don't set `language: haskell` here+# This Travis job script has been generated by a script via+#+# runghc make_travis_yml_2.hs '-o' '.travis.yml' '--irc-channel=irc.freenode.org#haskell-lens' '--no-no-tests-no-bench' '--no-installed' 'cabal.project'+#+# For more information, see https://github.com/hvr/multi-ghc-travis+#+language: c+sudo: false -# See also https://github.com/hvr/multi-ghc-travis for more information-env:- # we have to use CABALVER=1.16 for GHC<7.6 as well, as there's- # no package for earlier cabal versions in the PPA- - GHCVER=7.4.2 CABALVER=1.16- - GHCVER=7.6.3 CABALVER=1.16- - GHCVER=7.8.4 CABALVER=1.18- - GHCVER=7.10.1 CABALVER=1.22- - GHCVER=head CABALVER=1.22+git:+ submodules: false # whether to recursively clone submodules +notifications:+ irc:+ channels:+ - "irc.freenode.org#haskell-lens"+ skip_join: true+ template:+ - "\x0313contravariant\x03/\x0306%{branch}\x03 \x0314%{commit}\x03 %{build_url} %{message}"++cache:+ directories:+ - $HOME/.cabal/packages+ - $HOME/.cabal/store++before_cache:+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log+ # remove files that are regenerated by 'cabal update'+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.*+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx++ - rm -rfv $HOME/.cabal/packages/head.hackage++addons:+ apt:+ packages: &apt_packages+ - ghc-ppa-tools+ - hlint+ matrix:+ include:+ - compiler: "ghc-7.0.4"+ # env: TEST=--disable-tests BENCH=--disable-benchmarks+ addons: {apt: {packages: [*apt_packages,cabal-install-2.0,ghc-7.0.4], sources: [hvr-ghc]}}+ - compiler: "ghc-7.2.2"+ # env: TEST=--disable-tests BENCH=--disable-benchmarks+ addons: {apt: {packages: [*apt_packages,cabal-install-2.0,ghc-7.2.2], sources: [hvr-ghc]}}+ - compiler: "ghc-7.4.2"+ # env: TEST=--disable-tests BENCH=--disable-benchmarks+ addons: {apt: {packages: [*apt_packages,cabal-install-2.0,ghc-7.4.2], sources: [hvr-ghc]}}+ - compiler: "ghc-7.6.3"+ # env: TEST=--disable-tests BENCH=--disable-benchmarks+ addons: {apt: {packages: [*apt_packages,cabal-install-2.0,ghc-7.6.3], sources: [hvr-ghc]}}+ - compiler: "ghc-7.8.4"+ # env: TEST=--disable-tests BENCH=--disable-benchmarks+ addons: {apt: {packages: [*apt_packages,cabal-install-2.0,ghc-7.8.4], sources: [hvr-ghc]}}+ - compiler: "ghc-7.10.3"+ # env: TEST=--disable-tests BENCH=--disable-benchmarks+ addons: {apt: {packages: [*apt_packages,cabal-install-2.0,ghc-7.10.3], sources: [hvr-ghc]}}+ - compiler: "ghc-8.0.2"+ # env: TEST=--disable-tests BENCH=--disable-benchmarks+ addons: {apt: {packages: [*apt_packages,cabal-install-2.0,ghc-8.0.2], sources: [hvr-ghc]}}+ - compiler: "ghc-8.2.2"+ # env: TEST=--disable-tests BENCH=--disable-benchmarks+ addons: {apt: {packages: [*apt_packages,cabal-install-2.0,ghc-8.2.2], sources: [hvr-ghc]}}+ - compiler: "ghc-8.4.1"+ env: GHCHEAD=true+ addons: {apt: {packages: [*apt_packages,cabal-install-head,ghc-8.4.1], sources: [hvr-ghc]}}+ - compiler: "ghc-head"+ env: GHCHEAD=true+ addons: {apt: {packages: [*apt_packages,cabal-install-head,ghc-head], sources: [hvr-ghc]}}+ allow_failures:- - env: GHCVER=head CABALVER=1.22+ - compiler: "ghc-7.0.4"+ - compiler: "ghc-7.2.2"+ - compiler: "ghc-8.4.1"+ - compiler: "ghc-head" -# Note: the distinction between `before_install` and `install` is not-# important. before_install:- - travis_retry sudo add-apt-repository -y ppa:hvr/ghc- - travis_retry sudo apt-get update- - travis_retry sudo apt-get install cabal-install-$CABALVER ghc-$GHCVER- - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH- - cabal --version+ - HC=${CC}+ - HCPKG=${HC/ghc/ghc-pkg}+ - unset CC+ - ROOTDIR=$(pwd)+ - mkdir -p $HOME/.local/bin+ - "PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$HOME/local/bin:$PATH"+ - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))+ - echo $HCNUMVER install:- - travis_retry cabal update- - cabal install --only-dependencies- - travis_retry sudo apt-get -q -y install hlint || cabal install hlint+ - cabal --version+ - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"+ - BENCH=${BENCH---enable-benchmarks}+ - TEST=${TEST---enable-tests}+ - HADDOCK=${HADDOCK-true}+ - INSTALLED=${INSTALLED-true}+ - GHCHEAD=${GHCHEAD-false}+ - travis_retry cabal update -v+ - "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"+ - rm -fv cabal.project cabal.project.local+ # Overlay Hackage Package Index for GHC HEAD: https://github.com/hvr/head.hackage+ - |+ if $GHCHEAD; then+ sed -i.bak 's/-- allow-newer:.*/allow-newer: *:base, *:template-haskell, *:ghc, *:Cabal/' ${HOME}/.cabal/config -# Here starts the actual work to be performed for the package under-# test; any command which exits with a non-zero exit code causes the-# build to fail.+ echo 'repository head.hackage' >> ${HOME}/.cabal/config+ echo ' url: http://head.hackage.haskell.org/' >> ${HOME}/.cabal/config+ echo ' secure: True' >> ${HOME}/.cabal/config+ echo ' root-keys: 07c59cb65787dedfaef5bd5f987ceb5f7e5ebf88b904bbd4c5cbdeb2ff71b740' >> ${HOME}/.cabal/config+ echo ' 2e8555dde16ebd8df076f1a8ef13b8f14c66bad8eafefd7d9e37d0ed711821fb' >> ${HOME}/.cabal/config+ echo ' 8f79fd2389ab2967354407ec852cbe73f2e8635793ac446d09461ffb99527f6e' >> ${HOME}/.cabal/config+ echo ' key-threshold: 3' >> ${HOME}/.cabal.config++ cabal new-update head.hackage -v+ fi+ - grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'+ - "printf 'packages: \".\"\\n' > cabal.project"+ - cat cabal.project+ - if [ -f "./configure.ac" ]; then+ (cd "." && autoreconf -i);+ fi+ - rm -f cabal.project.freeze+ - cabal new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project" --dep -j2 all+ - rm -rf "."/.ghc.environment.* "."/dist+ - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)++# Here starts the actual work to be performed for the package under test;+# any command which exits with a non-zero exit code causes the build to fail. script:- # -v2 provides useful information for debugging- - cabal configure -v2+ # test that source-distributions can be generated+ - (cd "." && cabal sdist)+ - mv "."/dist/contravariant-*.tar.gz ${DISTDIR}/+ - cd ${DISTDIR} || false+ - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;+ - "printf 'packages: contravariant-*/*.cabal\\n' > cabal.project"+ - cat cabal.project - # this builds all libraries and executables- # (including tests/benchmarks)- - cabal build - # tests that a source-distribution can be generated- - cabal sdist- - hlint src --cpp-define HLINT+ # build & run tests, build benchmarks+ - cabal new-build -w ${HC} ${TEST} ${BENCH} all - # check that the generated source-distribution can be built & installed- - export SRC_TGZ=$(cabal info . | awk '{print $2 ".tar.gz";exit}') ;- cd dist/;- if [ -f "$SRC_TGZ" ]; then- cabal install --force-reinstalls "$SRC_TGZ";- else- echo "expected '$SRC_TGZ' not found";- exit 1;- fi+ # cabal check+ - (cd contravariant-* && cabal check) -notifications:- irc:- channels:- - "irc.freenode.org#haskell-lens"- skip_join: true- template:- - "\x0313contravariant\x03/\x0306%{branch}\x03 \x0314%{commit}\x03 %{build_url} %{message}"+ # haddock+ - rm -rf ./dist-newstyle+ - if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} all; else echo "Skipping haddock generation";fi++ # hlint+ - (cd contravariant-* && hlint src --cpp-define=HLINT)++# REGENDATA ["-o",".travis.yml","--irc-channel=irc.freenode.org#haskell-lens","--no-no-tests-no-bench","--no-installed","cabal.project"]+# EOF
CHANGELOG.markdown view
@@ -1,3 +1,10 @@+1.4.1 [2018.01.18]+------------------+* Add `Semigroup` and `Monoid` instances for `Predicate`.+* Add lots of documentation explaining `Contravariant`, `Divisible`, and+ `Decidable`.+* Fix some dodgy CPP usage that caused the build to fail on Eta.+ 1.4 --- * Improved the performance of `Deciding` at the cost of downgrading it to `Trustworthy`.@@ -42,6 +49,10 @@ 1.2.0.1 ----- * Fix build on GHC 7.0.4++1.2+-----+* Renamed `Data.Functor.Contravariant.Applicative` to `Data.Functor.Contravariant.Divisible` 1.1.1 -----
HLint.hs view
@@ -1,2 +1,3 @@ ignore "Eta reduce"-+ignore "Use const"+ignore "Use first"
+ README.markdown view
@@ -0,0 +1,17 @@+contravariant+=============++[](https://hackage.haskell.org/package/contravariant)+[](http://travis-ci.org/ekmett/contravariant)++Haskell 98 contravariant functors++Contact Information+-------------------++Contributions and bug reports are welcome!++Please feel free to contact me through github or on the #haskell IRC channel on irc.freenode.net.++-Edward Kmett+
contravariant.cabal view
@@ -1,6 +1,6 @@ name: contravariant category: Control, Data-version: 1.4+version: 1.4.1 license: BSD3 cabal-version: >= 1.6 license-file: LICENSE@@ -11,11 +11,21 @@ bug-reports: http://github.com/ekmett/contravariant/issues copyright: Copyright (C) 2007-2015 Edward A. Kmett synopsis: Contravariant functors-description: Contravariant functors+description: Contravariant functors. build-type: Simple+tested-with: GHC == 7.0.4+ , GHC == 7.2.2+ , GHC == 7.4.2+ , GHC == 7.6.3+ , GHC == 7.8.4+ , GHC == 7.10.3+ , GHC == 8.0.2+ , GHC == 8.2.2+ , GHC == 8.4.1 extra-source-files: .travis.yml CHANGELOG.markdown+ README.markdown HLint.hs source-repository head@@ -56,13 +66,15 @@ build-depends: base < 5, transformers >= 0.2 && < 0.6,- transformers-compat >= 0.3 && < 1,- void >= 0.6 && < 1+ transformers-compat >= 0.3 && < 1 + if !impl(ghc >= 7.9)+ build-depends: void >= 0.6 && < 1+ if flag(tagged) && !impl(ghc >= 7.7) build-depends: tagged >= 0.4.4 && < 1 - if flag(semigroups)+ if flag(semigroups) && !impl(ghc >= 7.11) build-depends: semigroups >= 0.15.2 && < 1 if flag(StateVar)
src/Data/Functor/Contravariant.hs view
@@ -95,7 +95,7 @@ import Data.Monoid (Monoid(..)) #endif -#ifdef MIN_VERSION_semigroups+#if defined(MIN_VERSION_semigroups) || __GLASGOW_HASKELL__ >= 711 import Data.Semigroup (Semigroup(..)) #endif @@ -118,8 +118,33 @@ import Prelude hiding ((.),id) --- | Any instance should be subject to the following laws:+-- | The class of contravariant functors. --+-- Whereas in Haskell, one can think of a 'Functor' as containing or producing+-- values, a contravariant functor is a functor that can be thought of as+-- /consuming/ values.+--+-- As an example, consider the type of predicate functions @a -> Bool@. One+-- such predicate might be @negative x = x < 0@, which+-- classifies integers as to whether they are negative. However, given this+-- predicate, we can re-use it in other situations, providing we have a way to+-- map values /to/ integers. For instance, we can use the @negative@ predicate+-- on a person's bank balance to work out if they are currently overdrawn:+--+-- @+-- newtype Predicate a = Predicate { getPredicate :: a -> Bool }+--+-- instance Contravariant Predicate where+-- contramap f (Predicate p) = Predicate (p . f)+-- | `- First, map the input...+-- `----- then apply the predicate.+--+-- overdrawn :: Predicate Person+-- overdrawn = contramap personBankBalance negative+-- @+--+-- Any instance should be subject to the following laws:+-- -- > contramap id = id -- > contramap f . contramap g = contramap (g . f) --@@ -137,7 +162,7 @@ (>$) = contramap . const -- | If 'f' is both 'Functor' and 'Contravariant' then by the time you factor in the laws--- of each of those classes, it can't actually use it's argument in any meaningful capacity.+-- of each of those classes, it can't actually use its argument in any meaningful capacity. -- -- This method is surprisingly useful. Where both instances exist and are lawful we have -- the following laws:@@ -285,6 +310,19 @@ instance Contravariant Predicate where contramap f g = Predicate $ getPredicate g . f +#if defined(MIN_VERSION_semigroups) || __GLASGOW_HASKELL__ >= 711+instance Semigroup (Predicate a) where+ Predicate p <> Predicate q = Predicate $ \a -> p a && q a+#endif++instance Monoid (Predicate a) where+ mempty = Predicate $ const True+#if defined(MIN_VERSION_semigroups) || __GLASGOW_HASKELL__ >= 711+ mappend = (<>)+#else+ mappend (Predicate p) (Predicate q) = Predicate $ \a -> p a && q a+#endif+ -- | Defines a total ordering on a type as per 'compare' -- -- This condition is not checked by the types. You must ensure that the supplied@@ -299,7 +337,7 @@ instance Contravariant Comparison where contramap f g = Comparison $ on (getComparison g) f -#ifdef MIN_VERSION_semigroups+#if defined(MIN_VERSION_semigroups) || __GLASGOW_HASKELL__ >= 711 instance Semigroup (Comparison a) where Comparison p <> Comparison q = Comparison $ mappend p q #endif@@ -344,7 +382,7 @@ instance Contravariant Equivalence where contramap f g = Equivalence $ on (getEquivalence g) f -#ifdef MIN_VERSION_semigroups+#if defined(MIN_VERSION_semigroups) || __GLASGOW_HASKELL__ >= 711 instance Semigroup (Equivalence a) where Equivalence p <> Equivalence q = Equivalence $ \a b -> p a b && q a b #endif@@ -375,7 +413,7 @@ instance Contravariant (Op a) where contramap f g = Op (getOp g . f) -#ifdef MIN_VERSION_semigroups+#if defined(MIN_VERSION_semigroups) || __GLASGOW_HASKELL__ >= 711 instance Semigroup a => Semigroup (Op a b) where Op p <> Op q = Op $ \a -> p a <> q a #endif
src/Data/Functor/Contravariant/Divisible.hs view
@@ -20,6 +20,15 @@ Divisible(..), divided, conquered, liftD -- * Contravariant Alternative , Decidable(..), chosen, lost+ -- * Mathematical definitions+ -- ** Divisible+ -- $divisible++ -- *** A note on 'conquer'+ -- $conquer++ -- ** Decidable+ -- $decidable ) where import Control.Applicative@@ -56,7 +65,7 @@ import Data.Proxy #endif -#if MIN_VERSION_StateVar+#ifdef MIN_VERSION_StateVar import Data.StateVar #endif @@ -73,48 +82,73 @@ -- -- A 'Divisible' contravariant functor is the contravariant analogue of 'Applicative'. ----- In denser jargon, a 'Divisible' contravariant functor is a monoid object in the category--- of presheaves from Hask to Hask, equipped with Day convolution mapping the Cartesian--- product of the source to the Cartesian product of the target.+-- Continuing the intuition that 'Contravariant' functors consume input, a 'Divisible'+-- contravariant functor also has the ability to be composed "beside" another contravariant+-- functor. ----- By way of contrast, an 'Applicative' functor can be viewed as a monoid object in the--- category of copresheaves from Hask to Hask, equipped with Day convolution mapping the--- Cartesian product of the source to the Cartesian product of the target.+-- Serializers provide a good example of 'Divisible' contravariant functors. To begin+-- let's start with the type of serializers for specific types: ----- Given the canonical diagonal morphism:+-- @+-- newtype Serializer a = Serializer { runSerializer :: a -> ByteString }+-- @ --+-- This is a contravariant functor:+-- -- @--- delta a = (a,a)+-- instance Contravariant Serializer where+-- contramap f s = Serializer (runSerializer s . f) -- @ ----- @'divide' 'delta'@ should be associative with 'conquer' as a unit+-- That is, given a serializer for @a@ (@s :: Serializer a@), and a way to turn+-- @b@s into @a@s (a mapping @f :: b -> a@), we have a serializer for @b@:+-- @contramap f s :: Serializer b@. --+-- Divisible gives us a way to combine two serializers that focus on different+-- parts of a structure. If we postulate the existance of two primitive+-- serializers - @string :: Serializer String@ and @int :: Serializer Int@, we+-- would like to be able to combine these into a serializer for pairs of+-- @String@s and @Int@s. How can we do this? Simply run both serializer and+-- combine their output!+-- -- @--- 'divide' 'delta' m 'conquer' = m--- 'divide' 'delta' 'conquer' m = m--- 'divide' 'delta' ('divide' 'delta' m n) o = 'divide' 'delta' m ('divide' 'delta' n o)+-- data StringAndInt = StringAndInt String Int+--+-- stringAndInt :: Serializer StringAndInt+-- stringAndInt = Serializer $ \(StringAndInt s i) ->+-- let sBytes = runSerializer string s+-- iBytes = runSerializer int i+-- in sBytes <> iBytes -- @ ----- With more general arguments you'll need to reassociate and project using the monoidal--- structure of the source category. (Here fst and snd are used in lieu of the more restricted--- lambda and rho, but this construction works with just a monoidal category.)+-- 'divide' is a generalization by also taking a 'contramap' like function to+-- split any @a@ into a pair. This conveniently allows you to target fields of+-- a record, for instance, by extracting the values under two fields and+-- combining them into a tuple. --+-- To complete the example, here is how to write @stringAndInt@ using a+-- @Divisible@ instance:+-- -- @--- 'divide' f m 'conquer' = 'contramap' ('fst' . f) m--- 'divide' f 'conquer' m = 'contramap' ('snd' . f) m--- 'divide' f ('divide' g m n) o = 'divide' f' m ('divide' 'id' n o) where--- f' a = case f a of (bc,d) -> case g bc of (b,c) -> (a,(b,c))+-- instance Divisible Serializer where+-- conquer = Serializer (const mempty)+--+-- divide toBC bSerializer cSerializer = Serializer $ \a ->+-- case toBC a of+-- (b, c) ->+-- let bBytes = runSerializer bSerializer b+-- cBytes = runSerializer cSerializer c+-- in bBytes <> cBytes+--+-- stringAndInt :: Serializer StringAndInt+-- stringAndInt =+-- divide (\(StringAndInt s i) -> (s, i)) string int -- @+-- class Contravariant f => Divisible f where divide :: (a -> (b, c)) -> f b -> f c -> f a- -- | The underlying theory would suggest that this should be:- --- -- @- -- conquer :: (a -> ()) -> f a- -- @- --- -- However, as we are working over a Cartesian category (Hask) and the Cartesian product, such an input- -- morphism is uniquely determined to be @'const' 'mempty'@, so we elide it.++ -- | Conquer acts as an identity for combining @Divisible@ functors. conquer :: f a -- |@@ -280,7 +314,7 @@ conquer = Proxy #endif -#if MIN_VERSION_StateVar+#ifdef MIN_VERSION_StateVar instance Divisible SettableStateVar where divide k (SettableStateVar l) (SettableStateVar r) = SettableStateVar $ \ a -> case k a of (b, c) -> l b >> r c@@ -302,26 +336,58 @@ -- * Contravariant Alternative -------------------------------------------------------------------------------- --- |+-- | A 'Decidable' contravariant functor is the contravariant analogue of 'Alternative'. ----- A 'Divisible' contravariant functor is a monoid object in the category of presheaves--- from Hask to Hask, equipped with Day convolution mapping the cartesian product of the--- source to the Cartesian product of the target.+-- Noting the superclass constraint that @f@ must also be 'Divisible', a @Decidable@+-- functor has the ability to "fan out" input, under the intuition that contravariant+-- functors consume input. --+-- In the dicussion for @Divisible@, an example was demonstrated with @Serializer@s,+-- that turn @a@s into @ByteString@s. @Divisible@ allowed us to serialize the /product/+-- of multiple values by concatenation. By making our @Serializer@ also @Decidable@-+-- we now have the ability to serialize the /sum/ of multiple values - for example+-- different constructors in an ADT.+--+-- Consider serializing arbitrary identifiers that can be either @String@s or @Int@s:+-- -- @--- 'choose' 'Left' m ('lose' f) = m--- 'choose' 'Right' ('lose' f) m = m--- 'choose' f ('choose' g m n) o = 'divide' f' m ('divide' 'id' n o) where--- f' bcd = 'either' ('either' 'id' ('Right' . 'Left') . g) ('Right' . 'Right') . f+-- data Identifier = StringId String | IntId Int -- @ ----- In addition, we expect the same kind of distributive law as is satisfied by the usual--- covariant 'Alternative', w.r.t 'Applicative', which should be fully formulated and--- added here at some point!-+-- We know we have serializers for @String@s and @Int@s, but how do we combine them+-- into a @Serializer@ for @Identifier@? Essentially, our @Serializer@ needs to+-- scrutinise the incoming value and choose how to serialize it:+--+-- @+-- identifier :: Serializer Identifier+-- identifier = Serializer $ \identifier ->+-- case identifier of+-- StringId s -> runSerializer string s+-- IntId i -> runSerializer int i+-- @+--+-- It is exactly this notion of choice that @Decidable@ encodes. Hence if we add+-- an instance of @Decidable@ for @Serializer@...+--+-- @+-- instance Decidable Serializer where+-- lose f = Serializer $ \a -> absurd (f a)+-- choose split l r = Serializer $ \a ->+-- either (runSerializer l) (runSerializer r) (split a)+-- @+--+-- Then our @identifier@ @Serializer@ is+--+-- @+-- identifier :: Serializer Identifier+-- identifier = choose toEither string int where+-- toEither (StringId s) = Left s+-- toEither (IntId i) = Right i+-- @ class Divisible f => Decidable f where- -- | The only way to win is not to play.+ -- | Acts as identity to 'choose'. lose :: (a -> Void) -> f a+ choose :: (a -> Either b c) -> f b -> f c -> f a -- |@@ -483,10 +549,72 @@ choose _ Proxy Proxy = Proxy #endif -#if MIN_VERSION_StateVar-instance Decidable SettableVar where+#ifdef MIN_VERSION_StateVar+instance Decidable SettableStateVar where lose k = SettableStateVar (absurd . k) choose k (SettableStateVar l) (SettableStateVar r) = SettableStateVar $ \ a -> case k a of Left b -> l b Right c -> r c #endif++-- $divisible+--+-- In denser jargon, a 'Divisible' contravariant functor is a monoid object in the category+-- of presheaves from Hask to Hask, equipped with Day convolution mapping the Cartesian+-- product of the source to the Cartesian product of the target.+--+-- By way of contrast, an 'Applicative' functor can be viewed as a monoid object in the+-- category of copresheaves from Hask to Hask, equipped with Day convolution mapping the+-- Cartesian product of the source to the Cartesian product of the target.+--+-- Given the canonical diagonal morphism:+--+-- @+-- delta a = (a,a)+-- @+--+-- @'divide' 'delta'@ should be associative with 'conquer' as a unit+--+-- @+-- 'divide' 'delta' m 'conquer' = m+-- 'divide' 'delta' 'conquer' m = m+-- 'divide' 'delta' ('divide' 'delta' m n) o = 'divide' 'delta' m ('divide' 'delta' n o)+-- @+--+-- With more general arguments you'll need to reassociate and project using the monoidal+-- structure of the source category. (Here fst and snd are used in lieu of the more restricted+-- lambda and rho, but this construction works with just a monoidal category.)+--+-- @+-- 'divide' f m 'conquer' = 'contramap' ('fst' . f) m+-- 'divide' f 'conquer' m = 'contramap' ('snd' . f) m+-- 'divide' f ('divide' g m n) o = 'divide' f' m ('divide' 'id' n o) where+-- f' a = case f a of (bc,d) -> case g bc of (b,c) -> (a,(b,c))+-- @++-- $conquer+-- The underlying theory would suggest that this should be:+--+-- @+-- conquer :: (a -> ()) -> f a+-- @+--+-- However, as we are working over a Cartesian category (Hask) and the Cartesian product, such an input+-- morphism is uniquely determined to be @'const' 'mempty'@, so we elide it.++-- $decidable+--+-- A 'Divisible' contravariant functor is a monoid object in the category of presheaves+-- from Hask to Hask, equipped with Day convolution mapping the cartesian product of the+-- source to the Cartesian product of the target.+--+-- @+-- 'choose' 'Left' m ('lose' f) = m+-- 'choose' 'Right' ('lose' f) m = m+-- 'choose' f ('choose' g m n) o = 'divide' f' m ('divide' 'id' n o) where+-- f' bcd = 'either' ('either' 'id' ('Right' . 'Left') . g) ('Right' . 'Right') . f+-- @+--+-- In addition, we expect the same kind of distributive law as is satisfied by the usual+-- covariant 'Alternative', w.r.t 'Applicative', which should be fully formulated and+-- added here at some point!