-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Isomorphism typeclass solving the conversion problem -- -- Lawful typeclass for lossless conversion between types. @package isomorphism-class @version 0.2 -- | Lawful solution to the conversion problem. -- --

Conversion problem

-- -- Have you ever looked for a toString function? How often do -- you import Data.Text.Lazy only to call its fromStrict? -- How about going thru the always fun sequence of importing -- Data.ByteString.Builder only to call its -- toLazyByteString and then importing -- Data.ByteString.Lazy only to call its toStrict? -- -- Those all are instances of one pattern. They are conversions between -- representations of the same information. Codebases that don't attempt -- to abstract over this pattern tend to be sprawling with this type of -- boilerplate. It's noise to the codereader, it's a burden to the -- implementor and the maintainer. -- --

Why another conversion library?

-- -- Many libraries exist that approach the conversion problem. However -- most of them provide lawless typeclasses leaving it up to the author -- of the instance to define what makes a proper conversion. This results -- in inconsistencies across instances, their behaviour not being evident -- to the user and no way to check whether an instance is correct. -- -- This library tackles this problem with a lawful typeclass, making it -- evident what any of its instances do and it provides property-tests -- for you to validate your instances. -- --

The insight

-- -- The key insight of this library is that if you add a requirement for -- the conversion to be lossless and to have a mirror conversion in the -- opposite direction, there usually appears to be only one way of -- defining it. That makes it very clear what the conversion does to the -- user and how to define it to the author of the conversion. It also -- gives a clear criteria for validating whether the instances are -- correct, which can be encoded in property-tests. -- -- That insight itself stems from an observation that almost all of the -- practical conversions in Haskell share a property: you can restore the -- original data from its converted form. E.g., you can get a text from a -- text-builder and you can create a text-builder from a text, you can -- convert a bytestring into a list of bytes and vice-versa, bytestring -- to/from bytearray, strict bytestring to/from lazy, list to/from -- sequence, sequence to/from vector, set of ints to/from int-set. In -- other words, it's always a two-way street with them and there's a lot -- of instances of this pattern. -- --

UX

-- -- A few other accidental findings like encoding this property with -- recursive typeclass constraints and fine-tuning for the use of the -- TypeApplications extension resulted in a terse and clear API. -- -- Essentially the whole API is just two functions: to and -- from. Both perform a conversion between two types. The only -- difference between them is in what the first type application -- parameter specifies. E.g.: -- --
--   toString = to @String
--   
-- --
--   fromText = from @Text
--   
-- -- The types are self-evident: -- --
--   > :t to @String
--   to @String :: Is String b => b -> String
--   
-- --
--   > :t from @Text
--   from @Text :: Is Text b => Text -> b
--   
-- -- In other words to and from let you explicitly specify -- either the source or the target type of a conversion when you need to -- help the type inferencer. -- -- Here are more practical examples: -- --
--   renderNameAndHeight :: Text -> Int -> Text
--   renderNameAndHeight name height =
--     from @StrictTextBuilder $
--       "Height of " <> to name <> " is " <> to (show height)
--   
-- --
--   combineEncodings :: ShortByteString -> ByteArray -> ByteString -> [Word8]
--   combineEncodings a b c =
--     from @Builder $
--       to a <> to b <> to c
--   
-- --

Partial conversions

-- -- Atop of all said this library also captures the notion of smart -- constructors via the IsSome class, which associates a total -- to conversion with partial maybeFrom. -- -- This captures the codec relationship between types. E.g., -- -- module IsomorphismClass -- | Bidirectional conversion between two types with no loss of -- information. -- -- The bidirectionality is encoded via a recursive dependency with -- arguments flipped. -- -- You can read the signature Is a b as "B is A". -- --

Laws

-- -- B is isomorphic to A if and only if there exists a -- conversion from B to A (to) and a conversion from -- A to B (from) such that: -- -- -- -- For testing whether your instances conform to these laws use -- isLawsProperties. -- --

Instance Definition

-- -- For each pair of isomorphic types (A and B) the compiler -- will require you to define four instances, namely: Is A B and -- Is B A as well as IsSome A B and IsSome B -- A. class (IsSome a b, Is b a) => Is a b -- | Evidence that all values of type sub form a subset of all -- values of type sup. -- -- From Wikipedia: -- -- In mathematics, a set A is a subset of a set B if all elements of A -- are also elements of B; B is then a superset of A. It is possible for -- A and B to be equal; if they are unequal, then A is a proper subset of -- B. The relationship of one set being a subset of another is called -- inclusion (or sometimes containment). A is a subset of B may also be -- expressed as B includes (or contains) A or A is included (or -- contained) in B. A k-subset is a subset with k elements. -- --

Laws

-- --

to is injective

-- -- For every two values of type sub that are not equal -- converting with to will always produce values that are not -- equal. -- --
--   \(a, b) -> a == b || to a /= to b
--   
-- --

maybeFrom is an inverse of to

-- -- For all values of sub converting to sup and then -- attempting to convert back to sub always succeeds and -- produces a value that is equal to the original. -- --
--   \a -> maybeFrom (to a) == Just a
--   
-- -- For testing whether your instances conform to these laws use -- isSomeLawsProperties. class IsSome sup sub -- | Convert a value a subset type to a superset type. -- -- This function is injective non-surjective. to :: IsSome sup sub => sub -> sup -- | Partial inverse of to. -- -- This function is a partial bijection. maybeFrom :: IsSome sup sub => sup -> Maybe sub ($dmmaybeFrom) :: (IsSome sup sub, IsSome sub sup) => sup -> Maybe sub -- | to in reverse direction. -- -- Particularly useful in combination with the TypeApplications -- extension, where it allows to specify the input type, e.g.: -- --
--   fromText :: Is Text b => Text -> b
--   fromText = from @Text
--   
-- -- The first type application of the to function on the other hand -- specifies the output data type. from :: Is a b => a -> b -- | Van-Laarhoven-style Prism, compatible with the "lens" library. isSomePrism :: (IsSome a b, Choice p, Applicative f) => p b (f b) -> p a (f a) -- | Van-Laarhoven-style Isomorphism, compatible with the "lens" library. isIso :: (Is a b, Profunctor p, Functor f) => p b (f b) -> p a (f a) -- | Helper for deriving common instances on types which have an instance -- of IsSome sup using the DerivingVia -- extension. -- -- E.g., -- --
--   newtype Percent = Percent Double
--     deriving newtype (Show, Eq, Ord)
--     deriving (Read, IsString, Arbitrary) via (ViaIsSome Double Percent)
--   
--   instance IsSome Double Percent where
--     to (Percent double) = double
--     maybeFrom double =
--       if double < 0 || double > 1
--         then Nothing
--         else Just (Percent double)
--   
-- -- In the code above all the instances that are able to construct the -- values of Percent are automatically derived based on the -- IsSome Double Percent instance. This guarantees that they -- only construct values that pass thru the checks defined in -- maybeFrom. newtype ViaIsSome sup sub ViaIsSome :: sub -> ViaIsSome sup sub -- | Properties testing whether an instance satisfies the laws of -- IsSome. -- -- The instance is identified via the proxy types that you provide. -- -- E.g., here's how you can integrate it into an Hspec test-suite: -- --
--   spec = do
--     describe "IsSome laws" do
--       traverse_
--         (uncurry prop)
--         (isSomeLawsProperties @Int32 @Int16 Proxy Proxy)
--   
isSomeLawsProperties :: (IsSome a b, Eq a, Eq b, Show a, Arbitrary b, Show b) => Proxy a -> Proxy b -> [(String, Property)] -- | Properties testing whether an instance satisfies the laws of -- Is. -- -- The instance is identified via the proxy types that you provide. -- -- E.g., here's how you can integrate it into an Hspec test-suite: -- --
--   spec = do
--     describe "Is laws" do
--       traverse_
--         (uncurry prop)
--         (isLawsProperties @Int32 @Word32 Proxy Proxy)
--   
isLawsProperties :: (Is a b, Eq a, Eq b, Arbitrary a, Show a, Arbitrary b, Show b) => Proxy a -> Proxy b -> [(String, Property)]