-- 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.,
--
--
-- - Every Int16 can be losslessly converted into
-- Int32, but not every Int32 can be losslessly
-- converted into Int16.
-- - Every Text can be converted into ByteString via
-- UTF-8 encoding, but not every ByteString forms a valid UTF-8
-- sequence.
-- - Every URL can be uniquely represented as Text, but most
-- Texts are not URLs unfortunately.
--
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:
--
--
-- - from . to = id - For all values of
-- B converting from B to A and then converting from
-- A to B produces a value that is identical to the
-- original.
-- - to . from = id - For all values of
-- A converting from A to B and then converting from
-- B to A produces a value that is identical to the
-- original.
--
--
-- 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
--
--
--
--
-- 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)]