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 +8/−1
- lawful-conversions.cabal +1/−1
- src/library/LawfulConversions.hs +3/−8
- src/library/LawfulConversions/Algebra.hs +83/−14
- src/library/LawfulConversions/Optics.hs +1/−1
- src/library/LawfulConversions/Properties.hs +28/−16
- src/library/LawfulConversions/Relations/ByteStringAndLazyText.hs +1/−1
- src/library/LawfulConversions/Relations/ByteStringAndString.hs +1/−1
- src/library/LawfulConversions/Relations/ByteStringAndText.hs +1/−1
- src/library/LawfulConversions/Relations/DayAndStrictTextBuilder.hs +2/−2
- src/library/LawfulConversions/Relations/LazyByteStringAndLazyText.hs +1/−1
- src/library/LawfulConversions/Relations/LazyByteStringAndString.hs +1/−1
- src/library/LawfulConversions/Relations/LazyByteStringAndText.hs +1/−1
- src/library/LawfulConversions/Relations/LazyTextAndString.hs +1/−1
- src/library/LawfulConversions/Relations/LazyTextBuilderAndString.hs +1/−1
- src/library/LawfulConversions/Relations/StrictTextBuilderAndString.hs +2/−2
- src/library/LawfulConversions/Relations/StrictTextBuilderAndUtcTime.hs +2/−2
- src/library/LawfulConversions/Relations/StringAndText.hs +1/−1
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