{"id":16336998,"url":"https://github.com/gvolpe/split-morphism","last_synced_at":"2025-03-22T23:32:31.182Z","repository":{"id":56878732,"uuid":"169935171","full_name":"gvolpe/split-morphism","owner":"gvolpe","description":":curly_loop: Split Morphisms","archived":false,"fork":false,"pushed_at":"2020-02-08T00:49:15.000Z","size":34,"stargazers_count":11,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-17T18:13:04.897Z","etag":null,"topics":["category","category-theory","epimorphism","haskell","lens","monomorphism","morphism","optics","prism"],"latest_commit_sha":null,"homepage":"http://hackage.haskell.org/package/split-morphism-0.1.0.0","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gvolpe.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-02-10T02:19:52.000Z","updated_at":"2024-07-06T08:06:08.000Z","dependencies_parsed_at":"2022-08-20T10:40:36.414Z","dependency_job_id":null,"html_url":"https://github.com/gvolpe/split-morphism","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvolpe%2Fsplit-morphism","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvolpe%2Fsplit-morphism/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvolpe%2Fsplit-morphism/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gvolpe%2Fsplit-morphism/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gvolpe","download_url":"https://codeload.github.com/gvolpe/split-morphism/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245036127,"owners_count":20550662,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["category","category-theory","epimorphism","haskell","lens","monomorphism","morphism","optics","prism"],"created_at":"2024-10-10T23:45:34.127Z","updated_at":"2025-03-22T23:32:30.885Z","avatar_url":"https://github.com/gvolpe.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"split-morphism\n==============\n\n[![CircleCI](https://circleci.com/gh/gvolpe/split-morphism/tree/master.svg?style=svg)](https://circleci.com/gh/gvolpe/split-morphism/tree/master)\n\nExperimental 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).\n\nFurther development (in Scala) can be found in the [Gemini Ocs3\nrepository](https://github.com/gemini-hlsw/ocs3/tree/v0.11.13/modules/core/shared/src/main/scala/gem/optics).\n\n## Non-Injective Optics\n\nStandard 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`.\n\nIf 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.\n\nThis [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.\n\nWhen 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.\n\nA `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.\n\n\n```\n               Wedge a b\n                 a ? b\n\n                   │                  Format a b\n          ┌────────┴────────┐       ∃ a ⊂ a | a \u003e b\n          │                 │\n                                           │\n    SplitMono a b      SplitEpi a b   ─────┤\n        a \u003c b             a \u003e b            │\n\n          │                 │         Prism a b\n          └────────┬────────┘       ∃ a ⊂ a | a = b\n                   │                       │\n                                           │\n                Iso a b   ─────────────────┘\n                 a = b\n```\n\nAdapted from the [Scala\nversion](https://github.com/gemini-hlsw/ocs3/blob/v0.11.13/modules/core/shared/src/main/scala/gem/optics/README.md).\n\n## Examples\n\nIt is recommended to have qualified import of the modules, otherwise you might have some issues..\n\n### Split Epimorphism\n\n```\nghci\u003e import qualified Control.Lens.SplitEpi as SE\nghci\u003e import Data.Maybe (fromMaybe)\nghci\u003e import Text.Read (readMaybe)\nghci\u003e let epi = SE.SplitEpi (fromMaybe 0 . readMaybe) show :: SE.SplitEpi String Integer\nghci\u003e SE.reverseGet epi 123\n\"123\"\nghci\u003e SE.get epi \"foo\"\n0\nghci\u003e SE.get epi \"87\"\n87\n```\n\n### Split Monomorphism\n\n```\nghci\u003e import qualified Control.Lens.SplitMono as SM\nghci\u003e let mono = SM.SplitMono toInteger fromInteger :: SM.SplitMono Int Integer\nghci\u003e SM.get mono 1234567890123456789\n1234567890123456789\nghci\u003e SM.reverseGet mono 1234567890123456789\n1234567890123456789\nghci\u003e SM.reverseGet mono 123456789012345678901234\n-7269072992350064654\n```\n\n### Format\n\n```\nghci\u003e import qualified Control.Lens.Format as F\nghci\u003e let format = F.Format (\\n -\u003e if n \u003e 0 then Just (n `mod` 2 == 0) else Nothing) (\\n -\u003e if n then 2 else 1) :: F.Format Int Bool\nghci\u003e F.getMaybe format 0\nNothing\nghci\u003e F.getMaybe format 1\nJust False\nghci\u003e F.getMaybe format 2\nJust True\nghci\u003e F.getMaybe format 3\nJust False\nghci\u003e F.reverseGet format True\n2\nghci\u003e F.reverseGet format False\n1\n```\n\n### Wedge\n\n```\nghci\u003e import qualified Control.Lens.SplitEpi as SE\nghci\u003e import qualified Control.Lens.SplitMono as SM\nghci\u003e import qualified Control.Lens.SplitMorphism as S\nghci\u003e import qualified Control.Lens.Wedge as W\nghci\u003e let epi = SE.SplitEpi fromInteger toInteger :: SE.SplitEpi Integer Int\nghci\u003e let mono = SM.SplitMono toInteger fromInteger :: SM.SplitMono Int Integer\nghci\u003e let wedge = epi `S.composeSplitEpiMono` mono :: Wedge Integer Integer\nghci\u003e W.get wedge 123\n123\nghci\u003e W.reverseGet  wedge 123\n123\nghci\u003e W.get wedge 123456789123456789000\n-5670419392510072312\nghci\u003e W.reverseGet wedge 123456789123456789000\n-5670419392510072312\nghci\u003e W.normalizeB wedge 123\n123\nghci\u003e W.normalizeA wedge 123\n123\n```\n\n### Invariant mapping\n\nAll the data types exposed by this library, namely `SplitEpi`, `SplitMono`, `Format` and `Wedge`, have instances of `InvariantFunctor`.\n\n#### SplitEpi\n\n```\nghci\u003e import Data.Functor.Invariant\nghci\u003e let epi' = invmap (+1) (+2) epi\nghci\u003e Se.reverseGet epi' 123\n\"125\"\nghci\u003e SE.get epi \"foo\"\n1\nghci\u003e SE.get epi' \"87\"\n88\n```\n\n#### Format\n\n```\nghci\u003e import Data.Functor.Invariant\nghci\u003e let format' = invmap not not format\nghci\u003e F.reverseGet format' True\n1\nghci\u003e F.reverseGet format' False\n2\n```\n\n### Conversions from Prism and Iso\n\n#### A `Prism` can be converted into a `Format`:\n\n```\nghci\u003e import Control.Lens\nghci\u003e import qualified Control.Lens.Format as F\nghci\u003e import GHC.Natural\nghci\u003e :{\nghci\u003e | nat :: Prism' Integer Natural\nghci\u003e | nat = prism toInteger $ \\ i -\u003e\nghci\u003e |    if i \u003c 0\nghci\u003e |    then Left i\nghci\u003e |    else Right (fromInteger i)\nghci\u003e | :}\nghci\u003e let f = F.fromPrism nat :: Format Integer Natural\n```\n\n#### An `Iso` can be converted into a `Format`, `SplitEpi`, `SplitMono` or `Wedge`:\n\n```\nghci\u003e import Control.Lens\nghci\u003e import qualified Control.Lens.SplitEpi as SE\nghci\u003e import qualified Control.Lens.SplitMono as SM\nghci\u003e let nonIso = non 5 :: Iso' (Maybe Int) Int\nghci\u003e let epi = SE.fromIso nonIso :: SplitEpi (Maybe Int) Int\nghci\u003e let mono = SM.fromIso nonIso :: SplitMono (Maybe Int) Int\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgvolpe%2Fsplit-morphism","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgvolpe%2Fsplit-morphism","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgvolpe%2Fsplit-morphism/lists"}