split-morphism: Split Epimorphisms and Monomorphisms

[ bsd3, data, generics, lenses, library ] [ Propose Tags ]

Please see the README on GitHub at https://github.com/gvolpe/split-morphism#readme


[Skip to Readme]
Versions [faq] 0.1.0.0, 0.1.0.1
Change log ChangeLog.md
Dependencies base (>=4.7 && <5), invariant (>=0.5.1 && <0.6), lens (==4.17.*) [details]
License BSD-3-Clause
Copyright 2019 Gabriel Volpe
Author Gabriel Volpe
Maintainer volpegabriel@gmail.com
Category Data, Lenses, Generics
Home page https://github.com/gvolpe/split-morphism#readme
Bug tracker https://github.com/gvolpe/split-morphism/issues
Source repo head: git clone https://github.com/gvolpe/split-morphism
Uploaded by gvolpe at Tue Sep 10 00:09:06 UTC 2019
Distributions NixOS:0.1.0.1
Downloads 156 total (50 in the last 30 days)
Rating 2.0 (votes: 1) [estimated by rule of succession]
Your Rating
  • λ
  • λ
  • λ
Status Hackage Matrix CI
Docs available [build log]
Last success reported on 2019-09-10 [all 1 reports]

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

For package maintainers and hackage trustees


Readme for split-morphism-0.1.0.1

[back to package description]

split-morphism

CircleCI

Experimental package representing Split Epimorphisms and Split Monomorphisms as presented by Rob Norris (@tpolecat) at Scala eXchange 2018.

Further developement (in Scala) can be found in the Gemini Ocs3 repository.

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 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.

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