diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+ChangeLog for `split-morphism`
+=============================
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
+
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,177 @@
+split-morphism
+==============
+
+[![CircleCI](https://circleci.com/gh/gvolpe/split-morphism/tree/master.svg?style=svg)](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
+```
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/split-morphism.cabal b/split-morphism.cabal
new file mode 100644
--- /dev/null
+++ b/split-morphism.cabal
@@ -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
diff --git a/src/Control/Lens/Format.hs b/src/Control/Lens/Format.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Lens/Format.hs
@@ -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)
+
diff --git a/src/Control/Lens/SplitEpi.hs b/src/Control/Lens/SplitEpi.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Lens/SplitEpi.hs
@@ -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)
+
diff --git a/src/Control/Lens/SplitMono.hs b/src/Control/Lens/SplitMono.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Lens/SplitMono.hs
@@ -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)
+
diff --git a/src/Control/Lens/SplitMorphism.hs b/src/Control/Lens/SplitMorphism.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Lens/SplitMorphism.hs
@@ -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
+
diff --git a/src/Control/Lens/Wedge.hs b/src/Control/Lens/Wedge.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Lens/Wedge.hs
@@ -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)
+
diff --git a/test/FormatSpec.hs b/test/FormatSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/FormatSpec.hs
@@ -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
+
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -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
+
diff --git a/test/SplitEpiSpec.hs b/test/SplitEpiSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/SplitEpiSpec.hs
@@ -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)
+
diff --git a/test/SplitMonoSpec.hs b/test/SplitMonoSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/SplitMonoSpec.hs
@@ -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)
+
diff --git a/test/WedgeSpec.hs b/test/WedgeSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/WedgeSpec.hs
@@ -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)
+
