split-morphism (empty) → 0.1.0.0
raw patch · 15 files changed
+678/−0 lines, 15 filesdep +QuickCheckdep +basedep +invariantsetup-changed
Dependencies added: QuickCheck, base, invariant, lens, split-morphism
Files
- ChangeLog.md +3/−0
- LICENSE +31/−0
- README.md +177/−0
- Setup.hs +2/−0
- split-morphism.cabal +65/−0
- src/Control/Lens/Format.hs +42/−0
- src/Control/Lens/SplitEpi.hs +41/−0
- src/Control/Lens/SplitMono.hs +41/−0
- src/Control/Lens/SplitMorphism.hs +49/−0
- src/Control/Lens/Wedge.hs +43/−0
- test/FormatSpec.hs +34/−0
- test/Spec.hs +16/−0
- test/SplitEpiSpec.hs +44/−0
- test/SplitMonoSpec.hs +44/−0
- test/WedgeSpec.hs +46/−0
+ ChangeLog.md view
@@ -0,0 +1,3 @@+ChangeLog for `split-morphism`+=============================+
+ LICENSE view
@@ -0,0 +1,31 @@+Copyright Gabriel Volpe (c) 2019++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Gabriel Volpe nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+
+ README.md view
@@ -0,0 +1,177 @@+split-morphism+==============++[](https://circleci.com/gh/gvolpe/split-morphism/tree/master)++Experimental package representing [Split Epimorphism](https://ncatlab.org/nlab/show/split+epimorphism)s and [Split Monomorphism](https://ncatlab.org/nlab/show/split+monomorphism)s as presented by [Rob Norris (@tpolecat)](https://github.com/tpolecat) at [Scala eXchange 2018](https://skillsmatter.com/skillscasts/11626-keynote-pushing-types-and-gazing-at-the-stars).++Further developement (in Scala) can be found in the [Gemini Ocs3+repository](https://github.com/gemini-hlsw/ocs3/tree/develop/modules/core/shared/src/main/scala/gem/optics).++## Non-Injective Optics++Standard 2-way optics deal with **invertible** mappings. `Iso a b` says that `a` and `b` are equal, so round-trips in either direction are identities. `Prism a b` says that there is some *subset* of `a` that is equal to `b`.++If we loosen the requirement that types be the same size we get a different kind of mapping, where the large type is squeezed into the small type in one direction or the other. An example is `Int ⟺ ByteString` by the standard widening/narrowing conversions. Note that the round-trip starting at `ByteString` is an identity, but the round-up starting at `Int` is merely **idempotent**: the first round-trip "normalizes" an `Int` into `ByteString` range and thereafter the round-trip is an identity.++This [phenomenon](https://ncatlab.org/nlab/show/split+epimorphism) is a thing, called a **split monomorphism** or a **split epimorphism** depending on which side is bigger. Note that every `Iso` is trivially a split where the idempotent round-trip happens to be an identity.++When we compose a `SplitMono` and a `SplitEpi` end-to-end in either direction we end up with a situation where neither round-trip is necessarily an identity but both are idempotent. I'm calling this a `Wedge` for lack of a better idea. Splits are trivially wedges where one of the idempotent round-trips happens to be an identity.++A `Format` is a weaker `Prism` where a *subset* of `a` forms a split epi with `b`. Every `Prism` is a `Format` where the split epi happens to be an `Iso`; and every `SplitEpi` forms a `Prism` where the subset of `a` is `a` itself.+++```+ Wedge a b+ a ? b++ │ Format a b+ ┌────────┴────────┐ ∃ a ⊂ a | a > b+ │ │+ │+ SplitMono a b SplitEpi a b ─────┤+ a < b a > b │++ │ │ Prism a b+ └────────┬────────┘ ∃ a ⊂ a | a = b+ │ │+ │+ Iso a b ─────────────────┘+ a = b+```++Adapted from the [Scala+version](https://github.com/gemini-hlsw/ocs3/blob/develop/modules/core/shared/src/main/scala/gem/optics/README.md).++## Examples++It is recommended to have qualified import of the modules, otherwise you might have some issues..++### Split Epimorphism++```+ghci> import qualified Control.Lens.SplitEpi as SE+ghci> import Data.Maybe (fromMaybe)+ghci> import Text.Read (readMaybe)+ghci> let epi = SE.SplitEpi (fromMaybe 0 . readMaybe) show :: SE.SplitEpi String Integer+ghci> SE.reverseGet epi 123+"123"+ghci> SE.get epi "foo"+0+ghci> SE.get epi "87"+87+```++### Split Monomorphism++```+ghci> import qualified Control.Lens.SplitMono as SM+ghci> let mono = SM.SplitMono toInteger fromInteger :: SM.SplitMono Int Integer+ghci> SM.get mono 1234567890123456789+1234567890123456789+ghci> SM.reverseGet mono 1234567890123456789+1234567890123456789+ghci> SM.reverseGet mono 123456789012345678901234+-7269072992350064654+```++### Format++```+ghci> import qualified Control.Lens.Format as F+ghci> let format = F.Format (\n -> if n > 0 then Just (n `mod` 2 == 0) else Nothing) (\n -> if n then 2 else 1) :: F.Format Int Bool+ghci> F.getMaybe format 0+Nothing+ghci> F.getMaybe format 1+Just False+ghci> F.getMaybe format 2+Just True+ghci> F.getMaybe format 3+Just False+ghci> F.reverseGet format True+2+ghci> F.reverseGet format False+1+```++### Wedge++```+ghci> import qualified Control.Lens.SplitEpi as SE+ghci> import qualified Control.Lens.SplitMono as SM+ghci> import qualified Control.Lens.SplitMorphism as S+ghci> import qualified Control.Lens.Wedge as W+ghci> let epi = SE.SplitEpi fromInteger toInteger :: SE.SplitEpi Integer Int+ghci> let mono = SM.SplitMono toInteger fromInteger :: SM.SplitMono Int Integer+ghci> let wedge = epi `S.composeSplitEpiMono` mono :: Wedge Integer Integer+ghci> W.get wedge 123+123+ghci> W.reverseGet wedge 123+123+ghci> W.get wedge 123456789123456789000+-5670419392510072312+ghci> W.reverseGet wedge 123456789123456789000+-5670419392510072312+ghci> W.normalizeB wedge 123+123+ghci> W.normalizeA wedge 123+123+```++### Invariant mapping++All the data types exposed by this library, namely `SplitEpi`, `SplitMono`, `Format` and `Wedge`, have instances of `InvariantFunctor`.++#### SplitEpi++```+ghci> import Data.Functor.Invariant+ghci> let epi' = invmap (+1) (+2) epi+ghci> Se.reverseGet epi' 123+"125"+ghci> SE.get epi "foo"+1+ghci> SE.get epi' "87"+88+```++#### Format++```+ghci> import Data.Functor.Invariant+ghci> let format' = invmap not not format+ghci> F.reverseGet format' True+1+ghci> F.reverseGet format' False+2+```++### Conversions from Prism and Iso++#### A `Prism` can be converted into a `Format`:++```+ghci> import Control.Lens+ghci> import qualified Control.Lens.Format as F+ghci> import GHC.Natural+ghci> :{+ghci> | nat :: Prism' Integer Natural+ghci> | nat = prism toInteger $ \ i ->+ghci> | if i < 0+ghci> | then Left i+ghci> | else Right (fromInteger i)+ghci> | :}+ghci> let f = F.fromPrism nat :: Format Integer Natural+```++#### An `Iso` can be converted into a `Format`, `SplitEpi`, `SplitMono` or `Wedge`:++```+ghci> import Control.Lens+ghci> import qualified Control.Lens.SplitEpi as SE+ghci> import qualified Control.Lens.SplitMono as SM+ghci> let nonIso = non 5 :: Iso' (Maybe Int) Int+ghci> let epi = SE.fromIso nonIso :: SplitEpi (Maybe Int) Int+ghci> let mono = SM.fromIso nonIso :: SplitMono (Maybe Int) Int+```+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ split-morphism.cabal view
@@ -0,0 +1,65 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: d09afe77299f307c5ed75a4c63139e7c4ea50c480a557e3835d09d16e3d14b5e++name: split-morphism+version: 0.1.0.0+synopsis: Split Epimorphisms and Monomorphisms+description: Please see the README on GitHub at <https://github.com/gvolpe/split-morphism#readme>+category: Data, Lenses, Generics+homepage: https://github.com/gvolpe/split-morphism#readme+bug-reports: https://github.com/gvolpe/split-morphism/issues+author: Gabriel Volpe+maintainer: volpegabriel@gmail.com+copyright: 2019 Gabriel Volpe+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ ChangeLog.md++source-repository head+ type: git+ location: https://github.com/gvolpe/split-morphism++library+ exposed-modules:+ Control.Lens.Format+ Control.Lens.SplitEpi+ Control.Lens.SplitMono+ Control.Lens.SplitMorphism+ Control.Lens.Wedge+ other-modules:+ Paths_split_morphism+ hs-source-dirs:+ src+ build-depends:+ base >=4.7 && <5+ , invariant >=0.5.1 && <0.6+ , lens >=4.17 && <4.18+ default-language: Haskell2010++test-suite split-morphism-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ FormatSpec+ SplitEpiSpec+ SplitMonoSpec+ WedgeSpec+ Paths_split_morphism+ hs-source-dirs:+ test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Widentities -Wredundant-constraints -Wmissing-export-lists -Wpartial-fields+ build-depends:+ QuickCheck >=2.12.6 && <2.13+ , base >=4.7 && <5+ , invariant >=0.5.1 && <0.6+ , lens >=4.17 && <4.18+ , split-morphism+ default-language: Haskell2010
+ src/Control/Lens/Format.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE TemplateHaskell #-}++module Control.Lens.Format where++import Control.Monad ((>=>))+import Control.Lens+import Data.Functor.Invariant.TH++{- | A normalizing optic, isomorphic to Prism but with different laws, specifically `getMaybe` needs not to+be injective; i.e., distinct inputs may have the same `getMaybe` result, which combined with a subsequent+`reverseGet` yields a normalized form for `a`. Composition with stronger optics (`Prism` and `Iso`) yields+another `Format`.+-}+data Format a b = Format+ { getMaybe :: a -> Maybe b+ , reverseGet :: b -> a+ }+$(deriveInvariant ''Format)++-- | `getMaybe` and `reverseGet`, yielding a normalized formatted value. Subsequent getMaybe/reverseGet cycles are idempotent.+normalize :: Format a b -> a -> Maybe a+normalize (Format f g) x = g <$> f x++-- | Compose with a Prism.+composePrism :: Format a b -> Prism' b c -> Format a c+composePrism (Format x y) p =+ Format (x >=> (^? p)) (y . review p)++-- | Compose with an Iso.+composeIso :: Format a b -> Iso' b c -> Format a c+composeIso (Format x y) i =+ Format (fmap (^. i) . x) (y . review i)++-- | A Prism is trivially a Format.+fromPrism :: Prism' a b -> Format a b+fromPrism p = Format (^? p) (review p)++-- | An Isomorphism is trivially a Format.+fromIso :: Iso' a b -> Format a b+fromIso i = Format (^? i) (review i)+
+ src/Control/Lens/SplitEpi.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE TemplateHaskell #-}++module Control.Lens.SplitEpi where++import Control.Lens+import Data.Functor.Invariant.TH++{- | A split epimorphism, which we can think of as a weaker `Iso` a b where `b` is a "smaller" type.+So `get . reverseGet` remains an identity but `reverseGet . get` is merely idempotent (i.e., it normalizes values in `a`).++The following statements hold:+ - `reverseGet` is a "section" of `get`,+ - `get` is a "retraction" of `reverseGet`,+ - `b` is a "retract" of `a`,+ - the pair `(get, reverseGet)` is a "splitting" of the idempotent `reverseGet . get`.+-}+data SplitEpi a b = SplitEpi+ { get :: a -> b+ , reverseGet :: b -> a+ }+$(deriveInvariant ''SplitEpi)++-- | `reverseGet . get`, yielding a normalized formatted value. Subsequent get/reverseGet cycles are idempotent.+normalize :: SplitEpi a b -> a -> a+normalize (SplitEpi f g) = g . f++-- | Compose with another SplitEpi.+composeSplitEpi :: SplitEpi a b -> SplitEpi b c -> SplitEpi a c+composeSplitEpi (SplitEpi x y) (SplitEpi q w) =+ SplitEpi (q . x) (y . w)++-- | Compose with an Iso.+composeIso :: SplitEpi a b -> Iso' b c -> SplitEpi a c+composeIso (SplitEpi x y) i =+ SplitEpi ((^. i) . x) (y . review i)++-- | An Isomorphism is trivially a SplitEpi.+fromIso :: Iso' a b -> SplitEpi a b+fromIso i = SplitEpi (^. i) (review i)+
+ src/Control/Lens/SplitMono.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE TemplateHaskell #-}++module Control.Lens.SplitMono where++import Control.Lens+import Data.Functor.Invariant.TH++{- | A split monomorphism, which we can think of as a weaker `Iso` a b where `a` is a "smaller" type.+So `reverseGet . get` remains an identity but `get . reverseGet` is merely idempotent (i.e., it normalizes values in `b`).++The following statements hold:+ - `reverseGet` is a "retraction" of `get`,+ - `get` is a "section" of `reverseGet`,+ - `a` is a "retract" of `b`,+ - the pair `(reverseGet, get)` is a "splitting" of the idempotent `get . reverseGet`.+-}+data SplitMono a b = SplitMono+ { get :: a -> b+ , reverseGet :: b -> a+ }+$(deriveInvariant ''SplitMono)++-- | `reverseGet . get`, yielding a normalized formatted value. Subsequent get/reverseGet cycles are idempotent.+normalize :: SplitMono a b -> b -> b+normalize (SplitMono f g) = f . g++-- | Compose with another SplitMono.+composeSplitMono :: SplitMono a b -> SplitMono b c -> SplitMono a c+composeSplitMono (SplitMono x y) (SplitMono q w) =+ SplitMono (q . x) (y . w)++-- | Compose with an Iso.+composeIso :: SplitMono a b -> Iso' b c -> SplitMono a c+composeIso (SplitMono x y) i =+ SplitMono ((^. i) . x) (y . review i)++-- | An Isomorphism is trivially a SplitMono.+fromIso :: Iso' a b -> SplitMono a b+fromIso i = SplitMono (^. i) (review i)+
+ src/Control/Lens/SplitMorphism.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE Rank2Types #-}++module Control.Lens.SplitMorphism (+ reverseEpi+ , reverseMono+ , composeSplitEpiMono+ , composeSplitEpiPrism+ , composeSplitMonoEpi+ , epiAsWedge+ , monoAsWedge+ ) where++import Control.Lens+import Control.Lens.Format (Format (..))+import Control.Lens.SplitEpi (SplitEpi (..))+import Control.Lens.SplitMono (SplitMono (..))+import Control.Lens.Wedge (Wedge (..))++-- | Swapping `Control.Lens.SplitEpi.get` and `Control.Lens.SplitEpi.reverseGet` yields a `SplitMono`.+reverseEpi :: SplitEpi a b -> SplitMono b a+reverseEpi (SplitEpi x y) = SplitMono y x++-- | Swapping `Control.Lens.SplitMono.get` and `Control.Lens.SplitMono.reverseGet` yields a `SplitEpi`.+reverseMono :: SplitMono a b -> SplitEpi b a+reverseMono (SplitMono x y) = SplitEpi y x++-- | Composition between `SplitMono` and `SplitEpi`.+composeSplitMonoEpi :: SplitMono a b -> SplitEpi b c -> Wedge a c+composeSplitMonoEpi (SplitMono x y) (SplitEpi q w) =+ Wedge (q . x) (y . w)++-- | Composition between `SplitEpi` and `SplitMono`.+composeSplitEpiMono :: SplitEpi a b -> SplitMono b c -> Wedge a c+composeSplitEpiMono (SplitEpi x y) (SplitMono q w) =+ Wedge (q . x) (y . w)++-- | Composition between `SplitEpi` and `Prism`.+composeSplitEpiPrism :: SplitEpi a b -> Prism' b c -> Format a c+composeSplitEpiPrism (SplitEpi x y) p =+ Format ((^? p) . x) (y . review p)++-- | Conversion from `SplitEpi` to `Wedge`.+epiAsWedge :: SplitEpi a b -> Wedge a b+epiAsWedge (SplitEpi x y) = Wedge x y++-- | Conversion from `SplitMono` to `Wedge`.+monoAsWedge :: SplitMono a b -> Wedge a b+monoAsWedge (SplitMono x y) = Wedge x y+
+ src/Control/Lens/Wedge.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE TemplateHaskell #-}++module Control.Lens.Wedge where++import Control.Lens+import Data.Functor.Invariant.TH++{- | Composition of a `Control.Lens.SplitMono.SplitMono` and a `Control.Lens.SplitEpi.SplitEpi`, yielding an even weaker structure where neither+`reverseGet . get` and `get . reverseGet` is an identity but both are idempotent.+-}+data Wedge a b = Wedge+ { get :: a -> b+ , reverseGet :: b -> a+ }+$(deriveInvariant ''Wedge)++-- | Normalize `a` via a round-trip through `b`.+normalizeA :: Wedge a b -> a -> a+normalizeA w = reverseGet w . get w++-- | Normalize `b` via a round-trip through `a`.+normalizeB :: Wedge a b -> b -> b+normalizeB w = get w . reverseGet w++-- | Swapping `get` and `reverseGet` yields a Wedge.+reverse :: Wedge a b -> Wedge b a+reverse (Wedge f g) = Wedge g f++-- | Compose with another Wedge.+composeWedge :: Wedge a b -> Wedge b c -> Wedge a c+composeWedge (Wedge x y) (Wedge q w) =+ Wedge (q . x) (y . w)++-- | Compose with an Iso.+composeIso :: Wedge a b -> Iso' b c -> Wedge a c+composeIso (Wedge x y) i =+ Wedge ((^. i) . x) (y . review i)++-- | An Isomorphism is trivially a Wedge.+fromIso :: Iso' a b -> Wedge a b+fromIso i = Wedge (^. i) (review i)+
+ test/FormatSpec.hs view
@@ -0,0 +1,34 @@+module FormatSpec (+ checkProps+ ) where++import Control.Lens.Format+import Test.QuickCheck++-- Our example Format injects ints into "positive and even"+example :: Format Int Bool+example = Format+ { getMaybe = \n -> if n > 0 then Just (n `mod` 2 == 0) else Nothing+ , reverseGet = \b -> if b then 2 else 1+ }++prop_normalize :: Int -> Bool+prop_normalize x =+ (normalize example x >>= getMaybe example) == getMaybe example x++prop_parse_round_trip :: Int -> Bool+prop_parse_round_trip x =+ (reverseGet example <$> (oa >>= getMaybe example)) == oa+ where+ oa = normalize example x++prop_format_round_trip :: Bool -> Bool+prop_format_round_trip x =+ getMaybe example (reverseGet example x) == Just x++checkProps :: IO ()+checkProps = do+ quickCheck prop_normalize+ quickCheck prop_parse_round_trip+ quickCheck prop_format_round_trip+
+ test/Spec.hs view
@@ -0,0 +1,16 @@+module Main (+ main+ ) where++import qualified FormatSpec as F+import qualified SplitEpiSpec as SE+import qualified SplitMonoSpec as SM+import qualified WedgeSpec as W++main :: IO ()+main = do+ F.checkProps+ SE.checkProps+ SM.checkProps+ W.checkProps+
+ test/SplitEpiSpec.hs view
@@ -0,0 +1,44 @@+module SplitEpiSpec (+ checkProps+ ) where++import Control.Lens.SplitEpi+import Data.Maybe (fromMaybe)+import Test.QuickCheck+import Text.Read (readMaybe)++epi1 :: SplitEpi String Integer+epi1 = SplitEpi (fromMaybe 0 . readMaybe) show++epi2 :: SplitEpi Integer Int+epi2 = SplitEpi fromInteger toInteger++epi3 :: SplitEpi String Int+epi3 = epi1 `composeSplitEpi` epi2++prop_normalize :: Eq b => SplitEpi a b -> a -> Bool+prop_normalize epi x =+ get epi (normalize epi x) == get epi x++prop_normalized_get_round_trip :: Eq a => SplitEpi a b -> a -> Bool+prop_normalized_get_round_trip epi x =+ (reverseGet epi . get epi) x' == x'+ where+ x' = normalize epi x++prop_reverse_get_round_trip :: Eq b => SplitEpi a b -> b -> Bool+prop_reverse_get_round_trip epi x =+ (get epi . reverseGet epi) x == x++checkProps :: IO ()+checkProps = do+ quickCheck (prop_normalize epi1)+ quickCheck (prop_normalize epi2)+ quickCheck (prop_normalize epi3)+ quickCheck (prop_normalized_get_round_trip epi1)+ quickCheck (prop_normalized_get_round_trip epi2)+ quickCheck (prop_normalized_get_round_trip epi3)+ quickCheck (prop_reverse_get_round_trip epi1)+ quickCheck (prop_reverse_get_round_trip epi2)+ quickCheck (prop_reverse_get_round_trip epi3)+
+ test/SplitMonoSpec.hs view
@@ -0,0 +1,44 @@+module SplitMonoSpec (+ checkProps+ ) where++import Control.Lens.SplitMono+import Data.Maybe (fromMaybe)+import Test.QuickCheck+import Text.Read (readMaybe)++mono1 :: SplitMono Int Integer+mono1 = SplitMono toInteger fromInteger++mono2 :: SplitMono Integer String+mono2 = SplitMono show (fromMaybe 0 . readMaybe)++mono3 :: SplitMono Int String+mono3 = mono1 `composeSplitMono` mono2++prop_normalize :: Eq a => SplitMono a b -> b -> Bool+prop_normalize mono x =+ reverseGet mono (normalize mono x) == reverseGet mono x++prop_normalized_reverse_get_round_trip :: Eq b => SplitMono a b -> b -> Bool+prop_normalized_reverse_get_round_trip mono x =+ (get mono . reverseGet mono) x' == x'+ where+ x' = normalize mono x++prop_get_round_trip :: Eq a => SplitMono a b -> a -> Bool+prop_get_round_trip mono x =+ (reverseGet mono . get mono) x == x++checkProps :: IO ()+checkProps = do+ quickCheck (prop_normalize mono1)+ quickCheck (prop_normalize mono2)+ quickCheck (prop_normalize mono3)+ quickCheck (prop_normalized_reverse_get_round_trip mono1)+ quickCheck (prop_normalized_reverse_get_round_trip mono2)+ quickCheck (prop_normalized_reverse_get_round_trip mono3)+ quickCheck (prop_get_round_trip mono1)+ quickCheck (prop_get_round_trip mono2)+ quickCheck (prop_get_round_trip mono3)+
+ test/WedgeSpec.hs view
@@ -0,0 +1,46 @@+module WedgeSpec (+ checkProps+ ) where++import qualified Control.Lens.SplitEpi as SE+import qualified Control.Lens.SplitMono as SM+import qualified Control.Lens.SplitMorphism as S+import Control.Lens.Wedge+import Test.QuickCheck++epi :: SE.SplitEpi Integer Int+epi = SE.SplitEpi fromInteger toInteger++mono :: SM.SplitMono Int Integer+mono = SM.SplitMono toInteger fromInteger++wedge :: Wedge Integer Integer+wedge = epi `S.composeSplitEpiMono` mono++prop_normalize_a :: Eq b => Wedge a b -> a -> Bool+prop_normalize_a w x =+ get w (normalizeA w x) == get w x++prop_normalize_b :: Eq a => Wedge a b -> b -> Bool+prop_normalize_b w x =+ reverseGet w (normalizeB w x) == reverseGet w x++prop_normalized_reverse_get_round_trip :: Eq b => Wedge a b -> b -> Bool+prop_normalized_reverse_get_round_trip w x =+ (get w . reverseGet w) x' == x'+ where+ x' = normalizeB w x++prop_normalized_get_round_trip :: Eq a => Wedge a b -> a -> Bool+prop_normalized_get_round_trip w x =+ (reverseGet w . get w) x' == x'+ where+ x' = normalizeA w x++checkProps :: IO ()+checkProps = do+ quickCheck (prop_normalize_a wedge)+ quickCheck (prop_normalize_b wedge)+ quickCheck (prop_normalized_reverse_get_round_trip wedge)+ quickCheck (prop_normalized_get_round_trip wedge)+