-- Hoogle documentation, generated by Haddock
-- See Hoogle, http://www.haskell.org/hoogle/
-- | Isomorphism typeclass solving the conversion problem
--
-- Isomorphism typeclass solving the conversion problem
@package isomorphism-class
@version 0.3
-- | 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 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 a property-test
-- for you to validate your instances.
--
-- The laws
--
-- 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.:
--
--
-- toText = to @Text
--
--
--
-- fromBuilder = from @Builder
--
--
-- The types are self-evident:
--
--
-- > :t to @Text
-- to @Text :: IsomorphicTo Text b => b -> Text
--
--
--
-- > :t from @Builder
-- from @Builder :: IsomorphicTo Builder b => Builder -> 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 or the reader.
--
-- Examples
--
--
-- combineEncodings :: ShortByteString -> ByteArray -> ByteString -> [Word8]
-- combineEncodings a b c =
-- from @Builder $
-- to a <> to b <> to c
--
--
--
-- renderNameAndHeight :: Text -> Int -> Text
-- renderNameAndHeight name height =
-- from @StrictTextBuilder $
-- "Height of " <> to name <> " is " <> fromString (show height)
--
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 IsomorphicTo a b as "B is
-- isomorphic to 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.
--
--
-- Testing
--
-- For testing whether your instances conform to these laws use
-- isomorphicToProperties.
--
-- Instance Definition
--
-- For each pair of isomorphic types (A and B) the compiler
-- will require you to define two instances, namely: IsomorphicTo A
-- B and IsomorphicTo B A.
class (IsomorphicTo b a) => IsomorphicTo a b
-- | Convert a value into an isomophic type.
to :: IsomorphicTo a b => b -> a
-- | to in reverse direction.
--
-- Particularly useful in combination with the TypeApplications
-- extension, where it allows to specify the input type, e.g.:
--
--
-- fromText :: IsomorphicTo 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 :: IsomorphicTo a b => a -> b
-- | Van-Laarhoven-style Isomorphism, compatible with the "lens" library.
isomorphicToIso :: (IsomorphicTo a b, Profunctor p, Functor f) => p b (f b) -> p a (f a)
-- | Properties testing whether an instance satisfies the laws of
-- IsomorphicTo.
--
-- 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 "IsomorphicTo laws" do
-- traverse_
-- (uncurry prop)
-- (isomorphicToProperties @Int32 @Word32 Proxy Proxy)
--
isomorphicToProperties :: (IsomorphicTo a b, Eq a, Eq b, Arbitrary a, Show a, Arbitrary b, Show b) => Proxy a -> Proxy b -> [(String, Property)]