packages feed

coercible-subtypes (empty) → 0.1.0.0

raw patch · 7 files changed

+342/−0 lines, 7 filesdep +basedep +profunctorssetup-changed

Dependencies added: base, profunctors

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for coercible-subtypes++## 0.1.0.0 -- 2020-04-08++* First version.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2020, Koji Miyazato++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 Koji Miyazato 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,107 @@+# coercible-subtypes++This library provides unidirectional (one-way) variant of [Coercion](https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Type-Coercion.html).++The variant is a type `Sub` defined in `Data.Type.Coercion.Sub`.+`Sub a b` can be used to convert a type `a` to another type `b`.++```+upcastWith :: Sub a b -> a -> b+```++For all `Sub a b` values, the runtime representation of `a` and+`b` values are same, so `upcastWith` do not require any computation+to return `b` value, just [coerce](https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Coerce.html)s+GHC to treat a value of `a` as type `b`.+This feature is not different to `Coercion`.++The difference is that while `Coercion` represents+bidirectional relation, `Sub` represents unidirectional relation.+`Coercion a b` and its underlying type class `Coercible a b` witnesse you can coerce both `a` to `b` and `b` to `a`.+Unlike that, `Sub a b` only allows you to coerce `a` to `b`, not `b` to `a`.++## Usage Example++To use this library effectively, it must be used at two places: a library+and its user code. For this example, let's assume they are written by two people,+a *library author* and a *user*.++The library author writes a module `RightTriangle` below.++```+module RightTriangle(Triangle(), toEdges, getEdges, fromEdges) where+  import Data.Coerce+  import Data.Type.Coercion.Sub+  +  newtype Triangle = MkTriangle (Int, Int, Int)+  +  -- | Triangles can be coerced into 3-tuples of Ints+  toEdges :: Sub Triangle (Int, Int, Int)+  toEdges = sub+  +  getEdges :: Triangle -> (Int, Int, Int)+  getEdges = coerce+  +  -- | Creates right triangle from lengths of edges (a,b,c)+  -- +  -- >  *+  -- >  |\ c+  -- > a| \+  -- >  *--*+  -- >   b+  --+  -- (a^2 + b^2 == c^2) must hold.+  fromEdges :: (Int, Int, Int) -> Maybe Triangle+  fromEdges = {- Omit -}+```++The author wants to protect the invariant condition `a^2 + b^2 == c^2`.+For that purpose, the author can't export the constructor of `Triangle`.+Because it is symmetric, `Coercion Triangle (Int,Int,Int)` can't be exported either.++The user is building an application using `RightTriangle` module.++```+module Main where+  import Data.Map (Map)+  import RightTriangle+  +  import Data.Type.Coercion.Sub+  +  main :: IO ()+  main = ......+```++In this application, the user has to convert `Map String Triangle` to+`Map String (Int, Int, Int)`, revealing the edge lengths of the triangles.+While it is easy to do so with `fmap getEdges`,+using `fmap` here can make an entire copy of the Map<sup>[†](#footnote)</sup>.+This is wasted work and memory. Instead, the user can use `mapR toEdges` to get+`Sub (Map String Triangle) (Map String (Int, Int, Int))`+and then `upcastWith` to perform zero cost coercion over `Map`.++## Comparison against other methods++There are some other methods to achive the goal of this library.++* Just give up coercion++  * This is just for better performance, so not doing it+    is always an option.++* Rewrite rules++  * Rewrite rules based method is currently employed, and working at our hand.+    So, it is possible you don't need this library at all.+  +  * The downside is whether it works or not is on the provider of the+    "container" type in use, and GHC doing expected optimizations.+    Without reading source codes and examining the GHC optimization result (e.g. `-ddump-rule-firings`),+    you can't be sure you are doing the conversion zero-cost.++--------++<a id="footnote">†</a> For `Data.Map`, which [containers](https://hackage.haskell.org/package/containers)+package provides, can optimize `fmap` away via proper inlining and rewrite rules. The purpose of this library+is turning optimizations into explicit codes, or handling the cases when the container type in use does not+provide such an opportunity via rewrite rules.</small>
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ coercible-subtypes.cabal view
@@ -0,0 +1,35 @@+cabal-version:       2.2+name:                coercible-subtypes+version:             0.1.0.0+stability:           experimental+synopsis:            Coercible but only in one direction+description: Newtype wrapper 'Data.Type.Coercion.Sub.Sub'+             around 'Data.Type.Coercion.Coercion'+             to represent unidirectional coercion,+             and combinators for it, like 'Data.Type.Coercion.Sub.mapR'+             which extends coercion over covariant @Functor@.++homepage:            https://github.com/viercc/coercible-subtypes+bug-reports:         https://github.com/viercc/coercible-subtypes/issues+license:             BSD-3-Clause+license-file:        LICENSE+author:              Koji Miyazato+maintainer:          viercc@gmail.com+copyright:           (c) 2020 Koji Miyazato+category:            Data+build-type:          Simple+extra-source-files:  CHANGELOG.md, README.md++source-repository HEAD+  type:     git+  location: https://github.com/viercc/coercible-subtypes+  branch:   master++library+  exposed-modules:     Data.Type.Coercion.Sub,+                       Data.Type.Coercion.Sub.Internal+  build-depends:       base >=4.12 && <4.15,+                       profunctors+  hs-source-dirs:      src+  default-language:    Haskell2010+  ghc-options:         -Wall -Wcompat
+ src/Data/Type/Coercion/Sub.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE GADTs                 #-}+{-# LANGUAGE PolyKinds             #-}+{-# LANGUAGE QuantifiedConstraints #-}+{-# LANGUAGE RankNTypes            #-}+module Data.Type.Coercion.Sub(+  {- | @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.++  This is convenient for newtype wrappers which give additional guarantees.++  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)++  If you want to provide this type from a library you maintain,+  you would want to keep @Range@ abstract from outside of the module.++  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.+  +  -}+  Sub(),+  sub, toSub, upcastWith, equiv, gequiv,++  coercionIsSub,++  mapR, contramapR,+  bimapR, dimapR+) where++import           Data.Coerce+import           Data.Type.Coercion++import           Data.Bifunctor                  (Bifunctor)+import           Data.Functor.Contravariant      (Contravariant)+import           Data.Profunctor                 (Profunctor)++import           Data.Type.Coercion.Sub.Internal++-- | Make a witness for type-safe casting which respects direction.+sub :: Coercible a b => Sub a b+sub = Sub Coercion++toSub :: Coercion a b -> Sub a b+toSub = Sub++-- | Type-safe cast+upcastWith :: Sub a b -> a -> b+upcastWith (Sub Coercion) = coerce++-- | All 'Coercion' can be seen as 'Sub'+coercionIsSub :: Sub (Coercion a b) (Sub a b)+coercionIsSub = Sub Coercion++-- | `Sub` relation in both direction means there is `Coercion` relation.+equiv :: Sub a b -> Sub b a -> Coercion a b+equiv ab ba = gequiv ab ba Coercion++-- | Generalized 'equiv'+gequiv :: Sub a b -> Sub b a -> (Coercible a b => r) -> r+gequiv (Sub Coercion) (Sub Coercion) k = k++{-++Note: evaluating both arguments of `equiv` is necessary.+One might notice the following typechecks.++    equiv :: Sub a b -> Sub b a -> Coercion a b+    equiv (Sub Coercion) _ = Coercion++But this implementation allows inverting `Sub a b` circumventing the restriction;++    bad :: Sub a b -> Sub b a+    bad ab =+      let ba = upcastWith coercionIsSub (equiv ab ba)+      in ba++This is prevented by evaluating both arguments of `equiv`, making `bad ab` a bottom.++-}++-----------------------------++-- | Extend subtype relation covariantly.+mapR :: ( forall x x'. Coercible x x' => Coercible (t x) (t x')+        , Functor t)+     => Sub a b -> Sub (t a) (t b)+mapR (Sub Coercion) = Sub Coercion++-- | Extend subtype relation contravariantly+contramapR :: ( forall x x'. Coercible x x' => Coercible (t x) (t x')+              , Contravariant t)+           => Sub a b -> Sub (t b) (t a)+contramapR (Sub Coercion) = Sub Coercion++bimapR :: ( forall x x' y y'.+              (Coercible x x', Coercible y y') => Coercible (t x y) (t x' y')+          , Bifunctor t)+       => Sub a a' -> Sub b b' -> Sub (t a b) (t a' b')+bimapR (Sub Coercion) (Sub Coercion) = Sub Coercion++dimapR :: ( forall x x' y y'.+              (Coercible x x', Coercible y y') => Coercible (t x y) (t x' y')+          , Profunctor t)+       => Sub a a' -> Sub b b' -> Sub (t a' b) (t a b')+dimapR (Sub Coercion) (Sub Coercion) = Sub Coercion
+ src/Data/Type/Coercion/Sub/Internal.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE GADTs                 #-}+{-# LANGUAGE InstanceSigs          #-}+{-# LANGUAGE PolyKinds             #-}++{- |++This module exposes internals of "Data.Type.Coercion.Sub".++Using this module allows to violate the premises 'Sub' 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.Sub.Internal(+  Sub(..)+) where++import           Control.Category+import           Prelude                    hiding (id, (.))++import           Data.Type.Coercion++newtype Sub (a :: k) (b :: k) = Sub { getSub :: Coercion a b }+  deriving (Eq, Ord, Show)+-- It is intentional to omit some instances.+--+-- TestCoercion instance should not exist.+-- Knowing `Sub a b` and `Sub a c` should not conclude+-- `Coercible b c`.+--+-- Among instances `Coercion` has, Enum, Bounded, and Read are+-- excluded because they allows to make new value of `Sub a b`.+-- Constructing `Sub a b` values must be done through+-- combinators provided by this module or exported for+-- abstract type under library author's careful choice.++instance Category Sub where+  id :: Sub a a+  id = Sub Coercion++  (.) :: Sub b c -> Sub a b -> Sub a c+  Sub Coercion . Sub Coercion = Sub Coercion