-- | -- = Conversions -- -- The main part of the API is two functions: 'to' and 'from'. Both -- perform a conversion between two types. The main difference between them -- is in what the first type application parameter specifies. E.g.: -- -- > toString = to @String -- -- > fromText = from @Text -- -- The types should be self-evident: -- -- > > :t to @String -- > to @String :: IsSome String b => b -> String -- -- > > :t from @Text -- > from @Text :: IsMany 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 or the reader. -- -- == Examples -- -- @ -- combineEncodings :: 'Data.ByteString.Short.ShortByteString' -> 'Data.Primitive.ByteArray' -> ['Word8'] -> 'Data.ByteString.Lazy.ByteString' -- combineEncodings a b c = -- 'from' @'Data.ByteString.Builder.Builder' $ -- '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'. -- -- 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 'Text's are not URLs unfortunately. -- -- - UTCTime, JSON, Email, etc. -- -- == Examples -- -- Here's an example of implementing the Smart Constructor pattern. -- -- > module Percent (Percent) where -- > -- > import LawfulConversions -- > -- > newtype Percent = Percent Double -- > -- > instance IsSome Double Percent where -- > to (Percent double) = double -- > maybeFrom double = -- > if double < 0 || double > 1 -- > then Nothing -- > else Just (Percent double) -- -- 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 = -- > if double < 0 -- > then Percent 0 -- > else if double > 1 -- > then Percent 1 -- > else Percent double -- -- However declaring an instance of 'Is' would be incorrect, because this conversion is partial. -- Namely, while every @Percent@ value can be losslessly transformed into 'Double', not every 'Double' can be losslessly transformed into @Percent@. module LawfulConversions ( -- * Typeclasses IsSome (..), IsMany (..), Is, -- * Optics isSomePrism, isManyIso, isIso, -- * Instance derivation -- | Proxy data-types useful for deriving various standard instances using the @DerivingVia@ extension. module LawfulConversions.Proxies, -- * Testing module LawfulConversions.Properties, ) where import LawfulConversions.Classes import LawfulConversions.Optics import LawfulConversions.Properties import LawfulConversions.Proxies import LawfulConversions.Relations ()