packages feed

coercible-subtypes 0.1.1.0 → 0.2.0.0

raw patch · 8 files changed

+278/−29 lines, 8 filesdep ~basePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Data.Type.Coercion.Related: coercionIsRelated :: Sub (Coercion a b) (Related a b)
+ Data.Type.Coercion.Related: data Related (a :: k) (b :: k)
+ Data.Type.Coercion.Related: informRelation :: Related a b -> (Coercible a b => Related x y) -> Related x y
+ Data.Type.Coercion.Related: related :: Coercible a b => Related a b
+ Data.Type.Coercion.Related: subIsRelated :: Sub (Sub a b) (Related a b)
+ Data.Type.Coercion.Related: symRelated :: Related a b -> Related b a
+ Data.Type.Coercion.Related: undirected :: Sub a b -> (Coercible a b => Related x y) -> Related x y
+ Data.Type.Coercion.Related.Internal: Related :: Coercion a b -> Related (a :: k) (b :: k)
+ Data.Type.Coercion.Related.Internal: [getRelated] :: Related (a :: k) (b :: k) -> Coercion a b
+ Data.Type.Coercion.Related.Internal: instance Control.Category.Category Data.Type.Coercion.Related.Internal.Related
+ Data.Type.Coercion.Related.Internal: instance forall k (a :: k) (b :: k). GHC.Classes.Eq (Data.Type.Coercion.Related.Internal.Related a b)
+ Data.Type.Coercion.Related.Internal: instance forall k (a :: k) (b :: k). GHC.Classes.Ord (Data.Type.Coercion.Related.Internal.Related a b)
+ Data.Type.Coercion.Related.Internal: instance forall k (a :: k) (b :: k). GHC.Show.Show (Data.Type.Coercion.Related.Internal.Related a b)
+ Data.Type.Coercion.Related.Internal: instance forall k (a :: k) (b :: k). GHC.Types.Coercible a b => GHC.Enum.Bounded (Data.Type.Coercion.Related.Internal.Related a b)
+ Data.Type.Coercion.Related.Internal: instance forall k (a :: k) (b :: k). GHC.Types.Coercible a b => GHC.Enum.Enum (Data.Type.Coercion.Related.Internal.Related a b)
+ Data.Type.Coercion.Related.Internal: instance forall k (a :: k) (b :: k). GHC.Types.Coercible a b => GHC.Read.Read (Data.Type.Coercion.Related.Internal.Related a b)
+ Data.Type.Coercion.Related.Internal: newtype Related (a :: k) (b :: k)
+ Newtype.Intersection: IsIntersection :: !Sub z x -> !Sub z y -> (forall s. Sub s x -> Sub s y -> Sub s z) -> IsIntersection x y z
+ Newtype.Intersection: [conjunct] :: IsIntersection x y z -> forall s. Sub s x -> Sub s y -> Sub s z
+ Newtype.Intersection: [proj1] :: IsIntersection x y z -> !Sub z x
+ Newtype.Intersection: [proj2] :: IsIntersection x y z -> !Sub z y
+ Newtype.Intersection: associative :: IsIntersection x y xy -> IsIntersection xy z xy'z -> IsIntersection y z yz -> IsIntersection x yz x'yz -> Coercion xy'z x'yz
+ Newtype.Intersection: commutative :: IsIntersection x y z -> IsIntersection y x z
+ Newtype.Intersection: data IsIntersection x y z
+ Newtype.Intersection: idemp :: IsIntersection x x x
+ Newtype.Intersection: lesser :: Sub x y -> IsIntersection x y x
+ Newtype.Intersection: unique :: IsIntersection x y z -> IsIntersection x y z' -> Coercion z z'
+ Newtype.Intersection: withIntersection :: Related x y -> (forall xy. IsIntersection x y xy -> r) -> r
+ Newtype.Union: IsUnion :: !Sub x z -> !Sub y z -> (forall r. Sub x r -> Sub y r -> Sub z r) -> IsUnion x y z
+ Newtype.Union: [elim] :: IsUnion x y z -> forall r. Sub x r -> Sub y r -> Sub z r
+ Newtype.Union: [inl] :: IsUnion x y z -> !Sub x z
+ Newtype.Union: [inr] :: IsUnion x y z -> !Sub y z
+ Newtype.Union: associative :: IsUnion x y xy -> IsUnion xy z xy'z -> IsUnion y z yz -> IsUnion x yz x'yz -> Coercion xy'z x'yz
+ Newtype.Union: commutative :: IsUnion x y z -> IsUnion y x z
+ Newtype.Union: data IsUnion x y z
+ Newtype.Union: greater :: Sub x y -> IsUnion x y y
+ Newtype.Union: idemp :: IsUnion x x x
+ Newtype.Union: unique :: IsUnion x y z -> IsUnion x y z' -> Coercion z z'
+ Newtype.Union: withUnion :: Related x y -> (forall xy. IsUnion x y xy -> r) -> r

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@ # Revision history for coercible-subtypes +## 0.2.0.0 -- 2021-09-13++* Add `Related`+* Add `IsIntersection` and `IsUnion` facilities+ ## 0.1.1.0 -- 2021-08-24  * Add instances `Coercible a b => (Read (Sub a b), Enum (Sub a b), Bounded (Sub a b))`
coercible-subtypes.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.2 name:                coercible-subtypes-version:             0.1.1.0+version:             0.2.0.0 stability:           experimental synopsis:            Coercible but only in one direction description: Newtype wrapper 'Data.Type.Coercion.Sub.Sub'@@ -27,7 +27,11 @@  library   exposed-modules:     Data.Type.Coercion.Sub,-                       Data.Type.Coercion.Sub.Internal+                       Data.Type.Coercion.Sub.Internal,+                       Data.Type.Coercion.Related,+                       Data.Type.Coercion.Related.Internal,+                       Newtype.Union,+                       Newtype.Intersection   build-depends:       base >=4.12 && <4.17,                        profunctors   hs-source-dirs:      src
+ src/Data/Type/Coercion/Related.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE GADTs                 #-}+{-# LANGUAGE PolyKinds             #-}+{-# LANGUAGE RankNTypes            #-}+{- |+  'Related' type to witness that two types @a@ and @b@ shares the same runtime representation,+  but nothing about whether @a@ can be safely coerced to or from @b@.++  Unlike 'Coercion' or 'Sub', having a value of @Related a b@ does not allow to+  touch values of @a@ or @b@.++  This module is used alonside "Newtype.Union" and "Newtype.Intersection"+  to define union and intersection types of two 'Related' types.+-}+module Data.Type.Coercion.Related(+    Related(),+    related,+    subIsRelated, coercionIsRelated,+    symRelated, undirected, informRelation+) where++import Data.Coerce+import Data.Type.Coercion+import Data.Type.Coercion.Sub+import Data.Type.Coercion.Sub.Internal+import Data.Type.Coercion.Related.Internal++-- | @'Coercible' a b@ implies @Related a b@.+related :: Coercible a b => Related a b+related = Related Coercion++-- | @'Sub' a b@ implies @Related a b@.+subIsRelated :: Sub (Sub a b) (Related a b)+subIsRelated = sub++-- | @'Coercion' a b@ implies @'Sub' a b@, which implies @Related a b@.+coercionIsRelated :: Sub (Coercion a b) (Related a b)+coercionIsRelated = sub++-- | @Related@ is a symmetric relation.+symRelated :: Related a b -> Related b a+symRelated ab = informRelation ab related++-- | A direct consequence of 'informRelation' and 'subIsRelated'.+undirected :: Sub a b -> (Coercible a b => Related x y) -> Related x y+undirected ab = informRelation (upcastWith subIsRelated ab)++-- | Given @Related a b@, you can assume @Coercible a b@ for the purpose of proving+--   another @Related@ relation.+informRelation :: Related a b -> (Coercible a b => Related x y) -> Related x y+informRelation (Related Coercion) body = body
+ src/Data/Type/Coercion/Related/Internal.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE GADTs                 #-}+{-# LANGUAGE InstanceSigs          #-}+{-# LANGUAGE PolyKinds             #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE StandaloneDeriving #-}+{- |++This module exposes internals of "Data.Type.Coercion.Related".++Using this module allows to violate the premises 'Related' type provides.+It is advisable not to import this module if there is another way,+and to limit the amount of code accesible to this module.++-}+module Data.Type.Coercion.Related.Internal where++import           Control.Category+import           Prelude                    hiding (id, (.))++import           Data.Coerce+import           Data.Type.Coercion++-- | @Related a b@ witnesses @a@ and @b@ shares the same runtime representation,+--   but nothing about whether @a@ can be safely coerced to or from @b@.+-- +-- You can make 'Related' witnesses by using combinators in this module, or the methods of+-- the @'Category' Related@ instance: 'id' and @('.')@.+newtype Related (a :: k) (b :: k) = Related { getRelated :: Coercion a b }+  deriving stock (Eq, Ord, Show)+-- It is intentional to omit the 'TestCoercion' instance, existing for @Coercion@.+-- Knowing @Related a b@ and @Related a c@ should not conclude+-- @Coercible b c@.++deriving stock instance Coercible a b => Read (Related a b)+deriving newtype instance Coercible a b => Enum (Related a b)+deriving newtype instance Coercible a b => Bounded (Related a b)++instance Category Related where+  id :: Related a a+  id = Related Coercion++  (.) :: Related b c -> Related a b -> Related a c+  Related Coercion . Related Coercion = Related Coercion
src/Data/Type/Coercion/Sub.hs view
@@ -2,38 +2,35 @@ {-# LANGUAGE PolyKinds             #-} {-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE RankNTypes            #-}-module Data.Type.Coercion.Sub(-  {- | @Sub a b@ witnesses a zero-cost conversion @a -> b@.+{- | @'Sub' a b@ witnesses a zero-cost conversion @a -> b@. -  @Sub@ is a newtype wrapper around 'Coercion', but made opaque to hide-  the ability to 'Data.Coerce.coerce' into other direction.+=== Example -  This is convenient for newtype wrappers which give additional guarantees.+Think about the following code: -  As an example, think about the following code:+> -- | A pair @(x::a, y::a)@, but guaranteed @x <= y@+> newtype Range a = MkRange (a,a)+>+> getRange :: Range a -> (a,a)+> getRange = coerce+> mkRange :: Ord a => a -> a -> Range a+> mkRange x y = if x <= y then MkRange (x,y) else MkRange (y,x) -  > -- | A pair @(x::a, y::a)@, but guaranteed @x <= y@-  > newtype Range a = MkRange (a,a)-  >-  > getRange :: Range a -> (a,a)-  > getRange = coerce-  > mkRange :: Ord a => a -> a -> Range a-  > mkRange x y = if x <= y then MkRange (x,y) else MkRange (y,x)+If you want to provide this type from a library you maintain,+you would want to keep @Range@ abstract from outside of the module. -  If you want to provide this type from a library you maintain,-  you would want to keep @Range@ abstract from outside of the module.+A user may want to convert @[Range a]@ to @[(a,a)]@ without actually+traversing the list. This is possible if the user have access to the+internals, or you export a @Coercion (Range a) (a,a)@ value. But doing so+breaks the guarantee, because it also allows to use @Coercible@ in the other+direction, as in @coerce (10,5) :: Range Int@. -  An user may want to convert @[Range a]@ to @[(a,a)]@ without actually-  traversing the list. This is possible if the user have access to the-  internals, or you export a @Coercion (Range a) (a,a)@ value. But doing so-  breaks the guarantee, because it also allows to use @Coercible@ in the other-  direction, as in @coerce (10,5) :: Range Int@.+By exporting only @Sub (Range a) (a,a)@ value from your module,+this user can get @Sub [Range a] [(a,a)]@ using 'mapR',+without being able to make an invalid value. -  By exporting only @Sub (Range a) (a,a)@ value from your module,-  this user can get @Sub [Range a] [(a,a)]@ using 'mapR',-  without being able to make an invalid value.-  -  -}+-}+module Data.Type.Coercion.Sub(   Sub(),   sub, toSub, upcastWith, equiv, gequiv, @@ -53,10 +50,11 @@  import           Data.Type.Coercion.Sub.Internal --- | Make a witness for type-safe casting which respects direction.+-- | Make a directed witness of @'coerce' :: a -> b@. sub :: Coercible a b => Sub a b sub = Sub Coercion +-- | Make a directed witness of @'coerce' :: a -> b@, from a 'Coercion' value. toSub :: Coercion a b -> Sub a b toSub = Sub @@ -146,7 +144,7 @@        => Sub a a' -> Sub b b' -> Sub (t a' b) (t a b') dimapR (Sub Coercion) (Sub Coercion) = Sub Coercion --- | 'dimapR' specialized for '(->)'+-- | 'dimapR' specialized for functions @(->)@ arrR :: Sub a a' -> Sub b b' -> Sub (a' -> b) (a -> b') arrR = dimapR 
src/Data/Type/Coercion/Sub/Internal.hs view
@@ -23,6 +23,15 @@ import           Data.Coerce import           Data.Type.Coercion +{- |+  @Sub@ is a newtype wrapper around 'Coercion', but made opaque to hide+  the ability to 'Data.Coerce.coerce' into other direction.++  This is convenient for newtype wrappers which give additional guarantees.++  You can make @Sub@ witnesses by using combinators in this module, or the methods of+  the @'Category' Sub@ instance: 'id' and @('.')@.+-} newtype Sub (a :: k) (b :: k) = Sub { getSub :: Coercion a b }   deriving stock (Eq, Ord, Show) -- It is intentional to omit the 'TestCoercion' instance, existing for @Coercion@.
+ src/Newtype/Intersection.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE TypeOperators #-}+module Newtype.Intersection(+  module Data.Type.Coercion.Related,+  IsIntersection(..),+  withIntersection,+  +  unique, lesser, idemp, commutative, associative+) where++import Prelude hiding (id, (.))+import Control.Category+import Data.Type.Coercion.Sub (equiv, sub)+import Data.Type.Coercion.Sub.Internal+import Data.Type.Coercion.Related+import Data.Type.Coercion.Related.Internal+import Data.Type.Coercion ( Coercion(Coercion) )++-- | @IsIntersection x y z@ witnesses the fact:+--+--   * All @x, y, z@ share the same runtime representation+--   * @z@ is an intersection type of @x@ and @y@. In other words, the following three holds:+--+--       * @'Sub' z x@+--       * @Sub z y@+--       * For any type @s@ satisfying both of @(Sub s x, Sub s y)@, @Sub s z@.+data IsIntersection x y z = IsIntersection+  {+    proj1 :: !(Sub z x),+    proj2 :: !(Sub z y),+    conjunct :: forall s. Sub s x -> Sub s y -> Sub s z+  }++-- | For a pair of 'Related' types @x@ and @y@, make some (existentially quantified)+--   type @xy@ where @xy@ is an intersection type of @x, y@.+withIntersection :: Related x y -> (forall xy. IsIntersection x y xy -> r) -> r+withIntersection (Related Coercion) body =+    body IsIntersection{ proj1 = sub, proj2 = id, conjunct = seq }++-- | Two intersection types @z,z'@ of the same pair of types @x,y@ may be different,+--   but they are equivalent in terms of coercibility.+unique :: IsIntersection x y z -> IsIntersection x y z' -> Coercion z z'+unique xy xy' = equiv (conjunct xy' (proj1 xy) (proj2 xy)) (conjunct xy (proj1 xy') (proj2 xy'))++-- | When @Sub x y@, @x@ itself is an intersection type of @x, y@.+lesser :: Sub x y -> IsIntersection x y x+lesser l = IsIntersection{ proj1=id, proj2=l, conjunct= \sx !_ -> sx }+++-- | Intersection is idempotent.+--+--   Note: combining @idemp@ and 'unique', @IsIntersection x x z -> Coercible x z@ holds.+idemp :: IsIntersection x x x+idemp = lesser id++-- | Intersection is commutative.+--+--   Note: combining @commutative@ and 'unique', @IsIntersection x x z -> Coercible x z@ holds.+commutative :: IsIntersection x y z -> IsIntersection y x z+commutative xyz = IsIntersection{ proj1 = proj2 xyz, proj2 = proj1 xyz, conjunct = flip (conjunct xyz)}++-- | Intersection is associative.+associative :: IsIntersection x y xy -> IsIntersection xy z xy'z -> IsIntersection y z yz -> IsIntersection x yz x'yz -> Coercion xy'z x'yz+associative xy xy'z yz x'yz =+    equiv (conjunct x'yz (proj1 xy . proj1 xy'z) (conjunct yz (proj2 xy . proj1 xy'z) (proj2 xy'z)))+          (conjunct xy'z (conjunct xy (proj1 x'yz) (proj1 yz . proj2 x'yz)) (proj2 yz . proj2 x'yz))
+ src/Newtype/Union.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE TypeOperators #-}+module Newtype.Union(+  module Data.Type.Coercion.Related,+  IsUnion(..),++  withUnion,+  +  unique, greater, idemp, commutative, associative+) where++import Prelude hiding (id, (.))+import Control.Category+import Data.Type.Coercion.Sub (sub, equiv)+import Data.Type.Coercion.Sub.Internal+import Data.Type.Coercion.Related+import Data.Type.Coercion.Related.Internal+import Data.Type.Coercion ( Coercion(Coercion))++-- | @IsUnion x y z@ witnesses the fact:+--+--   * All @x, y, z@ share the same runtime representation+--   * @z@ is a union type of @x@ and @y@. In other words, the following three holds:+--+--     * @'Sub' x z@+--     * @Sub y z@+--     * For any type @r@ satisfying both of @(Sub x r, Sub y r)@, @Sub z r@.+data IsUnion x y z = IsUnion+  {+    inl :: !(Sub x z), -- ^ @x@ can be safely coerced to @z@+    inr :: !(Sub y z), -- ^ @y@ can be safely coerced to @z@+    elim :: forall r. Sub x r -> Sub y r -> Sub z r+      -- ^ Given both @x@ and @y@ can be safely coerced to @r@, too @z@ can.+  }++-- | For a pair of 'Related' types @x@ and @y@, make some (existentially quantified)+--   type @xy@ where @xy@ is a union type of @x, y@.+withUnion :: Related x y -> (forall xy. IsUnion x y xy -> r) -> r+withUnion (Related Coercion) body =+    body IsUnion{ inl = sub, inr = id, elim = seq }++-- | Two union types @z,z'@ of the same pair of types @x,y@ may be different,+--   but they are equivalent in terms of coercibility.+unique :: IsUnion x y z -> IsUnion x y z' -> Coercion z z'+unique xy xy' = equiv (elim xy (inl xy') (inr xy')) (elim xy' (inl xy) (inr xy))++-- | When @Sub x y@, @y@ itself is a union type of @x, y@.+greater :: Sub x y -> IsUnion x y y+greater l = IsUnion{ inl = l, inr = id, elim=seq }++-- | Union is idempotent.+--+--   Note: combining @idemp@ and 'unique', @IsUnion x x z -> Coercible x z@ holds.+idemp :: IsUnion x x x+idemp = greater id++-- | Union is commutative.+--+--   Note: combining @commutative@ and 'unique', @IsUnion x y xy -> IsUnion y x yx -> Coercible xy yx@ holds.+commutative :: IsUnion x y z -> IsUnion y x z+commutative xyz = IsUnion{ inl = inr xyz, inr = inl xyz, elim = flip (elim xyz) }++-- | Union is associative.+associative :: IsUnion x y xy -> IsUnion xy z xy'z -> IsUnion y z yz -> IsUnion x yz x'yz -> Coercion xy'z x'yz+associative xy xy'z yz x'yz =+    equiv (elim xy'z (elim xy (inl x'yz) (inr x'yz . inl yz)) (inr x'yz . inr yz))+          (elim x'yz (inl xy'z . inl xy) (elim yz (inl xy'z . inr xy) (inr xy'z)))