packages feed

lawful-conversions 0.2.0.1 → 0.3

raw patch · 18 files changed

+139/−56 lines, 18 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- LawfulConversions: ($dmfrom) :: (IsMany a b, IsSome b a) => a -> b
+ LawfulConversions: ($dmonfrom) :: (IsMany a b, IsSome b a) => a -> b
+ LawfulConversions: maybeTo :: forall b a. IsSome a b => a -> Maybe b
+ LawfulConversions: onfrom :: IsMany a b => a -> b
- LawfulConversions: from :: IsMany a b => a -> b
+ LawfulConversions: from :: forall b a. IsSome a b => b -> a
- LawfulConversions: isSomeProperties :: (IsSome a b, Eq a, Eq b, Show a, Show b, Arbitrary b) => Proxy a -> Proxy b -> [(String, Property)]
+ LawfulConversions: isSomeProperties :: (IsSome a b, Eq a, Eq b, Show a, Show b, Arbitrary a, Arbitrary b) => Proxy a -> Proxy b -> [(String, Property)]

Files

CHANGELOG.md view
@@ -1,5 +1,12 @@+# v0.3++## Breaking++- `from` of `IsMany` renamed to `onfrom`.+- New `from` function added, which is an alias to `to` with type arguments flipped.+ # v0.2  ## Breaking -- Added more tests in `isManyProperties` and an `Arbitrary` constraint on its parameter `a`+- Added more tests in `isManyProperties` and an `Arbitrary` constraint on its parameter `a`.
lawful-conversions.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: lawful-conversions-version: 0.2.0.1+version: 0.3 synopsis: Lawful typeclasses for bidirectional conversion between types category: Conversion homepage: https://github.com/nikita-volkov/lawful-conversions
src/library/LawfulConversions.hs view
@@ -30,13 +30,6 @@ --     'to' a <> 'to' b <> 'to' c -- @ ----- @--- renderNameAndHeight :: 'Text' -> 'Int' -> 'Text'--- renderNameAndHeight name height =---   'from' @'Data.Text.Encoding.StrictTextBuilder' $---     "Height of " <> 'from' name <> " is " <> 'from' (show height)--- @--- -- = Partial conversions -- -- This library also captures the pattern of smart constructors via the 'IsSome' class, which associates a total 'to' conversion with its partial inverse 'maybeFrom'.@@ -72,7 +65,7 @@ -- You can also expand upon that and provide a default handling of invalid values effectively providing a lossy canonicalizing conversion ([Surjection](https://en.wikipedia.org/wiki/Surjective_function)): -- -- > instance IsMany Double Percent where--- >   from double =+-- >   onfrom double = -- >     if double < 0 -- >       then Percent 0 -- >       else if double > 1@@ -88,6 +81,8 @@     Is,      -- * Combinators+    from,+    maybeTo,     onto,      -- * Optics
src/library/LawfulConversions/Algebra.hs view
@@ -23,6 +23,18 @@ -- -- > \b -> maybeFrom (to @a b) == Just b --+-- ==== 'maybeFrom' characterizes the image of 'to'+--+-- 'maybeFrom' succeeds exactly on values that are in the image of 'to':+--+-- > \a b -> maybeFrom a == Just b ==> to b == a+--+-- ==== Mathematical foundation+--+-- These laws establish that type @b@ forms a subset of type @a@ in the mathematical sense.+-- The 'to' function provides the canonical injection, while 'maybeFrom' recognizes which values of @a@+-- correspond to values from the subset @b@.+-- -- === Testing -- -- For testing whether your instances conform to these laws use 'LawfulConversions.isSomeProperties'.@@ -51,6 +63,34 @@   maybeFrom = const Nothing  -- |+-- Convert a value of a superset type to a subset type specifying the superset type first.+--+-- Alias to 'to' with the only difference in the argument order.+--+-- E.g.,+--+-- > fromText = from @Text+from :: forall b a. (IsSome a b) => b -> a+from = to++-- |+-- Try to convert a value of a superset type to a subset type specifying the target subset type first.+--+-- Alias to 'maybeFrom' with the only difference in the argument order.+--+-- Particularly useful in combination with the @TypeApplications@ extension,+-- where it allows to specify the target type, e.g.:+--+-- > maybeToInt16 :: Int32 -> Maybe Int16+-- > maybeToInt16 = maybeTo @Int16+--+-- E.g.,+--+-- > result = maybeTo @Percent someDouble+maybeTo :: forall b a. (IsSome a b) => a -> Maybe b+maybeTo = maybeFrom++-- | -- Lossy or canonicalizing conversion. -- Captures mappings from multiple alternative inputs into one output. --@@ -62,10 +102,24 @@ -- -- === Laws ----- ==== 'from' is an [inverse](https://en.wikipedia.org/wiki/Inverse_function) of 'to'+-- ==== 'onfrom' is an [inverse](https://en.wikipedia.org/wiki/Inverse_function) of 'to' ----- > \b -> b == from (to @a b)+-- > \b -> b == onfrom (to @a b) --+-- ==== 'onfrom' is [surjective](https://en.wikipedia.org/wiki/Surjective_function)+--+-- Every value of type @b@ can be obtained by applying 'onfrom' to some value of type @a@:+--+-- > \b -> exists a. onfrom @b a == b+--+-- Note: This property cannot be directly tested with QuickCheck as it requires existential quantification.+--+-- ==== Law hierarchy+--+-- 'IsMany' extends 'IsSome', so all laws from 'IsSome' also apply here.+-- The combination ensures that 'onfrom' provides a canonical (possibly lossy) conversion from @a@ to @b@,+-- while 'to' provides the lossless injection from @b@ to @a@.+-- -- === Testing -- -- For testing whether your instances conform to these laws use 'LawfulConversions.isManyProperties'.@@ -77,30 +131,34 @@   -- Particularly useful in combination with the @TypeApplications@ extension,   -- where it allows to specify the input type, e.g.:   ---  -- > fromText :: IsMany Text b => Text -> b-  -- > fromText = from @Text-  ---  -- The first type application of the 'to' function on the other hand specifies-  -- the output data type.+  -- > fromString :: IsMany String b => String -> b+  -- > fromString = onfrom @String   --   -- If you want to specify the output type instead, use 'onto'.-  from :: a -> b+  onfrom :: a -> b    -- |   -- Requires the presence of 'IsSome' in reverse direction.-  default from :: (IsSome b a) => a -> b-  from = to+  default onfrom :: (IsSome b a) => a -> b+  onfrom = to  -- |--- Alias to 'from', which lets you specify the target type of the conversion first using @TypeApplications@.+-- Alias to 'onfrom', which lets you specify the target type of the conversion first using @TypeApplications@. -- -- In mathematics @onto@ is another name for [Surjective function](https://en.wikipedia.org/wiki/Surjective_function). -- -- E.g., -- -- > lenientDecodeUtf8 = onto @Text+--+-- @+-- combineTexts :: 'Text' -> 'ByteString' -> 'Int' -> 'Text'+-- combineTexts name email height =+--   'from' @'Data.Text.Encoding.StrictTextBuilder' $+--     "Height of " <> 'to' name <> " is " <> 'onto' (show height) <> " and email is " <> 'onto' email+-- @ onto :: forall b a. (IsMany a b) => a -> b-onto = from+onto = onfrom  instance IsMany a a @@ -117,13 +175,24 @@ -- -- For all values of /b/ converting from /b/ to /a/ and then converting from /a/ to /b/ produces the original value: ----- > \b -> b == from (to @a b)+-- > \b -> b == from @b (to @a b) -- -- ==== 'to' is an [inverse](https://en.wikipedia.org/wiki/Inverse_function) of 'from' -- -- For all values of /a/ converting from /a/ to /b/ and then converting from /b/ to /a/ produces the original value: ----- > \a -> a == to (from @a @b a)+-- > \a -> a == to @a (from @b a)+--+-- ==== Mathematical relationship+--+-- These two laws together establish that 'to' and 'from' form a true [isomorphism](https://en.wikipedia.org/wiki/Isomorphism) between types @a@ and @b@.+-- Note that 'from' is implemented as 'to' from the reverse 'Is' instance, ensuring the symmetry required for isomorphisms.+--+-- ==== 'from' equals 'onfrom'+--+-- For isomorphic types, both ways of converting should be equivalent:+--+-- > \a -> from @b @a a == onfrom @a @b a -- -- === Testing --
src/library/LawfulConversions/Optics.hs view
@@ -14,7 +14,7 @@  -- | Van-Laarhoven-style Isomorphism, compatible with libraries like \"lens\" and \"optics\". isManyIso :: (IsMany a b, Profunctor p, Functor f) => p b (f b) -> p a (f a)-isManyIso = dimap from (fmap to)+isManyIso = dimap onfrom (fmap to)  -- | Van-Laarhoven-style Isomorphism, compatible with libraries like \"lens\" and \"optics\". isIso :: (Is a b, Profunctor p, Functor f) => p b (f b) -> p a (f a)
src/library/LawfulConversions/Properties.hs view
@@ -22,19 +22,26 @@ -- >       (uncurry prop) -- >       (isSomeProperties @Int32 @Int16 Proxy Proxy) isSomeProperties ::-  (IsSome a b, Eq a, Eq b, Show a, Show b, Arbitrary b) =>+  forall a b.+  (IsSome a b, Eq a, Eq b, Show a, Show b, Arbitrary a, Arbitrary b) =>   Proxy a ->   Proxy b ->   [(String, Property)] isSomeProperties aProxy bProxy =   [ ( "'to' is injective",-      property \a b ->-        a /= b ==>-          to' a =/= to' b+      property \b1 b2 ->+        b1 /= b2 ==>+          to' b1 =/= to' b2     ),     ( "'maybeFrom' is a partial inverse of 'to'",+      property \b ->+        maybeFrom' (to' b) === Just b+    ),+    ( "'maybeFrom' characterizes the image of 'to'",       property \a ->-        maybeFrom' (to' a) === Just a+        case maybeFrom' a of+          Just b -> to' b === a+          Nothing -> property True     )   ]   where@@ -60,24 +67,24 @@   Proxy b ->   [(String, Property)] isManyProperties aProxy bProxy =-  [ ( "'from' is an inverse of 'to'",-      property \b -> b === from' (to' b)+  [ ( "'onfrom' is an inverse of 'to'",+      property \b -> b === onfrom' (to' b)     ),-    ( "'from' is consistent with 'maybeFrom'",+    ( "'onfrom' is consistent with 'maybeFrom'",       property \(b :: b) ->         let a = to @a b-         in maybeFrom (to @a b) === Just (from @a @b a)+         in maybeFrom (to @a b) === Just (onfrom @a @b a)     ),-    ( "'to' after 'from' always succeeds with 'maybeFrom'",+    ( "'to' after 'onfrom' always succeeds with 'maybeFrom'",       property \a ->-        let b = from' a+        let b = onfrom' a          in maybeFrom (to' b) === Just b     )   ]     <> isSomeProperties aProxy bProxy   where     to' = as aProxy . to . as bProxy-    from' = as bProxy . from . as aProxy+    onfrom' = as bProxy . onfrom . as aProxy  -- | -- Properties testing whether an instance satisfies the laws of 'Is'.@@ -97,10 +104,15 @@   Proxy b ->   [(String, Property)] isProperties aProxy bProxy =-  ( "'to' is an inverse of 'from'",-    property \b -> b === to' (from' b)-  )-    : isManyProperties aProxy bProxy+  [ ( "'to' is an inverse of 'from'",+      property \b -> b === to' (from' b)+    ),+    ( "'from' equals 'onfrom'",+      property \a -> from' a === onfrom' a+    )+  ]+    <> isManyProperties aProxy bProxy   where     to' = as aProxy . to . as bProxy     from' = as bProxy . from . as aProxy+    onfrom' = as bProxy . onfrom . as aProxy
src/library/LawfulConversions/Relations/ByteStringAndLazyText.hs view
@@ -16,4 +16,4 @@  -- | Lenient UTF-8 decoding. instance IsMany ByteString Data.Text.Lazy.Text where-  from = Data.Text.Lazy.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode . Data.ByteString.Lazy.fromStrict+  onfrom = Data.Text.Lazy.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode . Data.ByteString.Lazy.fromStrict
src/library/LawfulConversions/Relations/ByteStringAndString.hs view
@@ -15,4 +15,4 @@  -- | Lenient UTF-8 decoding. instance IsMany ByteString String where-  from = Data.Text.unpack . Data.Text.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode+  onfrom = Data.Text.unpack . Data.Text.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode
src/library/LawfulConversions/Relations/ByteStringAndText.hs view
@@ -14,4 +14,4 @@  -- | Lenient UTF-8 decoding. instance IsMany ByteString Text where-  from = Data.Text.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode+  onfrom = Data.Text.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode
src/library/LawfulConversions/Relations/DayAndStrictTextBuilder.hs view
@@ -15,7 +15,7 @@  -- | Implements ISO-8601. instance IsSome Data.Text.Encoding.StrictTextBuilder Day where-  to = from . to @String+  to = onfrom . to @String   maybeFrom = maybeFrom @String . to  #elif MIN_VERSION_text(2,0,2)@@ -30,7 +30,7 @@  -- | Implements ISO-8601. instance IsSome Data.Text.Encoding.StrictBuilder Day where-  to = from . to @String+  to = onfrom . to @String   maybeFrom = maybeFrom @String . to  #endif
src/library/LawfulConversions/Relations/LazyByteStringAndLazyText.hs view
@@ -16,4 +16,4 @@  -- | Lenient UTF-8 decoding. instance IsMany Data.ByteString.Lazy.ByteString Data.Text.Lazy.Text where-  from = Data.Text.Lazy.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode+  onfrom = Data.Text.Lazy.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode
src/library/LawfulConversions/Relations/LazyByteStringAndString.hs view
@@ -16,4 +16,4 @@  -- | Lenient UTF-8 decoding. instance IsMany Data.ByteString.Lazy.ByteString String where-  from = Data.Text.Lazy.unpack . Data.Text.Lazy.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode+  onfrom = Data.Text.Lazy.unpack . Data.Text.Lazy.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode
src/library/LawfulConversions/Relations/LazyByteStringAndText.hs view
@@ -17,4 +17,4 @@  -- | Lenient UTF-8 decoding. instance IsMany Data.ByteString.Lazy.ByteString Data.Text.Text where-  from = Data.Text.Lazy.toStrict . Data.Text.Lazy.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode+  onfrom = Data.Text.Lazy.toStrict . Data.Text.Lazy.Encoding.decodeUtf8With Data.Text.Encoding.Error.lenientDecode
src/library/LawfulConversions/Relations/LazyTextAndString.hs view
@@ -12,4 +12,4 @@   maybeFrom = fmap Data.Text.Lazy.fromStrict . maybeFrom  instance IsMany String Data.Text.Lazy.Text where-  from = fromString+  onfrom = fromString
src/library/LawfulConversions/Relations/LazyTextBuilderAndString.hs view
@@ -13,4 +13,4 @@   maybeFrom = fmap Data.Text.Lazy.Builder.fromText . maybeFrom  instance IsMany String Data.Text.Lazy.Builder.Builder where-  from = fromString+  onfrom = fromString
src/library/LawfulConversions/Relations/StrictTextBuilderAndString.hs view
@@ -15,7 +15,7 @@   maybeFrom = fmap Data.Text.Encoding.textToStrictBuilder . maybeFrom  instance IsMany String Data.Text.Encoding.StrictTextBuilder where-  from =  Data.Text.Encoding.textToStrictBuilder . from+  onfrom =  Data.Text.Encoding.textToStrictBuilder . onfrom  #elif MIN_VERSION_text(2,0,2) @@ -29,6 +29,6 @@   maybeFrom = fmap Data.Text.Encoding.textToStrictBuilder . maybeFrom  instance IsMany String Data.Text.Encoding.StrictBuilder where-  from =  Data.Text.Encoding.textToStrictBuilder . from+  onfrom =  Data.Text.Encoding.textToStrictBuilder . onfrom  #endif
src/library/LawfulConversions/Relations/StrictTextBuilderAndUtcTime.hs view
@@ -15,7 +15,7 @@  -- | Implements ISO-8601. instance IsSome Data.Text.Encoding.StrictTextBuilder UTCTime where-  to = from . to @String+  to = onfrom . to @String   maybeFrom = maybeFrom @String . to  #elif MIN_VERSION_text(2,0,2)@@ -30,7 +30,7 @@  -- | Implements ISO-8601. instance IsSome Data.Text.Encoding.StrictBuilder UTCTime where-  to = from . to @String+  to = onfrom . to @String   maybeFrom = maybeFrom @String . to  #endif
src/library/LawfulConversions/Relations/StringAndText.hs view
@@ -16,4 +16,4 @@           else Nothing  instance IsMany String Text where-  from = Text.pack+  onfrom = Text.pack