Safe Haskell | None |
---|---|
Language | Haskell2010 |
LawfulConversions
Description
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 ::ShortByteString
->ByteArray
-> [Word8
] ->ByteString
combineEncodings a b c =from
@Builder
$to
a <>to
b <>to
c
renderNameAndHeight ::Text
->Int
->Text
renderNameAndHeight name height =from
@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 intoInt32
, but not everyInt32
can be losslessly converted intoInt16
. - Every
Text
can be converted intoByteString
via UTF-8 encoding, but not everyByteString
forms a valid UTF-8 sequence. - Every URL can be uniquely represented as
Text
, but mostText
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):
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
.
Synopsis
- class IsSome a b where
- class IsSome a b => IsMany a b where
- from :: a -> b
- class (IsMany a b, Is b a) => Is a b
- isSomePrism :: (IsSome a b, Choice p, Applicative f) => p b (f b) -> p a (f a)
- isManyIso :: (IsMany a b, Profunctor p, Functor f) => p b (f b) -> p a (f a)
- isIso :: (Is a b, Profunctor p, Functor f) => p b (f b) -> p a (f a)
- newtype ViaIsSome a b = ViaIsSome b
- isSomeProperties :: (IsSome a b, Eq a, Eq b, Show a, Show b, Arbitrary b) => Proxy a -> Proxy b -> [(String, Property)]
- isManyProperties :: (IsMany a b, Eq a, Eq b, Show a, Show b, Arbitrary b) => Proxy a -> Proxy b -> [(String, Property)]
- isProperties :: (Is a b, Eq a, Eq b, Show a, Show b, Arbitrary a, Arbitrary b) => Proxy a -> Proxy b -> [(String, Property)]
Typeclasses
class IsSome a b where Source #
Evidence that all values of type b
form a subset of all values of type a
.
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 b
that are not equal converting with to
produces values that are not equal as well:
\(b1, b2) -> b1 == b2 || to @a b1 /= to @a b2
maybeFrom
is a partial inverse of to
For all values of b
converting to a
and then attempting to convert back to b
always succeeds and produces a value that is equal to the original:
\b -> maybeFrom (to @a b) == Just b
Testing
For testing whether your instances conform to these laws use isSomeProperties
.
Minimal complete definition
Methods
Convert a value of a subset type to a superset type.
maybeFrom :: a -> Maybe b Source #
Partial inverse of to
.
Instances
IsSome ByteArray Builder Source # | |
IsSome ByteArray ByteString Source # | |
IsSome ByteArray ByteString Source # | |
IsSome ByteArray ShortByteString Source # | |
IsSome Int16 Word16 Source # | |
IsSome Int32 Word32 Source # | |
IsSome Int64 Word64 Source # | |
IsSome Int8 Word8 Source # | |
IsSome Word16 Int16 Source # | |
IsSome Word32 Int32 Source # | |
IsSome Word64 Int64 Source # | |
IsSome Word8 Int8 Source # | |
IsSome Builder ByteArray Source # | |
IsSome Builder ByteString Source # | |
IsSome Builder ByteString Source # | |
IsSome Builder ShortByteString Source # | |
IsSome ByteString ByteArray Source # | |
IsSome ByteString Builder Source # | |
IsSome ByteString ByteString Source # | |
IsSome ByteString ShortByteString Source # | |
IsSome ByteString Text Source # | UTF-8 codec. |
IsSome ByteString Text Source # | UTF-8 codec. |
IsSome ByteString String Source # | UTF-8 codec. |
IsSome ByteString ByteArray Source # | |
IsSome ByteString Builder Source # | |
IsSome ByteString ByteString Source # | |
IsSome ByteString ShortByteString Source # | |
IsSome ByteString Text Source # | UTF-8 codec. |
IsSome ByteString Text Source # | UTF-8 codec. |
IsSome ByteString String Source # | UTF-8 codec. |
IsSome ShortByteString ByteArray Source # | |
IsSome ShortByteString Builder Source # | |
IsSome ShortByteString ByteString Source # | |
IsSome ShortByteString ByteString Source # | |
IsSome Text Builder Source # | |
IsSome Text Text Source # | |
IsSome Text StrictBuilder Source # | |
IsSome Text Day Source # | Implements ISO-8601. |
IsSome Text UTCTime Source # | Implements ISO-8601. |
IsSome Text UUID Source # | |
IsSome Builder Text Source # | |
IsSome Builder Text Source # | |
IsSome Builder StrictBuilder Source # | |
IsSome Builder Day Source # | Implements ISO-8601. |
IsSome Builder UTCTime Source # | Implements ISO-8601. |
IsSome Builder UUID Source # | |
IsSome Text Text Source # | |
IsSome Text Builder Source # | |
IsSome Text StrictBuilder Source # | |
IsSome Text Day Source # | Implements ISO-8601. |
IsSome Text UTCTime Source # | Implements ISO-8601. |
IsSome Text UUID Source # | |
IsSome StrictBuilder Text Source # | |
IsSome StrictBuilder Builder Source # | |
IsSome StrictBuilder Text Source # | |
IsSome StrictBuilder Day Source # | Implements ISO-8601. |
IsSome StrictBuilder UTCTime Source # | Implements ISO-8601. |
IsSome StrictBuilder UUID Source # | |
IsSome String Text Source # | |
IsSome String Builder Source # | |
IsSome String Text Source # | |
IsSome String StrictBuilder Source # | |
IsSome String Day Source # | Implements ISO-8601. |
IsSome String UTCTime Source # | Implements ISO-8601. |
IsSome String UUID Source # | |
IsSome Int Word Source # | |
IsSome Word Int Source # | |
IsSome a Void Source # | The empty set has no elements, and therefore is vacuously a subset of any set. |
IsSome a a Source # | Every type is isomorphic to itself. |
IsSome ByteArray [Word8] Source # | |
IsSome Builder [Word8] Source # | |
IsSome ByteString [Word8] Source # | |
IsSome ByteString [Word8] Source # | |
IsSome ShortByteString [Word8] Source # | |
IsSome IntSet (Set Int) Source # | |
IsSome a b => IsSome a (ViaIsSome a b) Source # | |
IsSome b (ViaIsSome a b) Source # | |
IsSome (Set Int) IntSet Source # | |
IsSome [Word8] ByteArray Source # | |
IsSome [Word8] Builder Source # | |
IsSome [Word8] ByteString Source # | |
IsSome [Word8] ByteString Source # | |
IsSome [Word8] ShortByteString Source # | |
IsSome (Seq a) (Vector a) Source # | |
IsSome (Seq a) [a] Source # | |
IsSome (Vector a) (Seq a) Source # | |
IsSome (Vector a) [a] Source # | |
IsSome [a] (Seq a) Source # | |
IsSome [a] (Vector a) Source # | |
IsSome (IntMap v) (Map Int v) Source # | |
IsSome (ViaIsSome a b) b Source # | |
IsSome (Map Int v) (IntMap v) Source # | |
class IsSome a b => IsMany a b where Source #
Lossy or canonicalizing conversion. Captures mappings from multiple alternative inputs into one output.
E.g.,
ByteString
can be decoded intoText
with UTF-8 leniently, replacing the invalid chars with a default char.String
has a wider range of supported chars thanText
, so some chars get replaced too.
Laws
from
is an inverse of to
\b -> b == from (to @a b)
Testing
For testing whether your instances conform to these laws use isManyProperties
.
Minimal complete definition
Nothing
Methods
Possibly lossy inverse of to
.
Surjection from a
to b
.
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.
Instances
IsMany ByteArray Builder Source # | |
IsMany ByteArray ByteString Source # | |
IsMany ByteArray ByteString Source # | |
IsMany ByteArray ShortByteString Source # | |
IsMany Int16 Word16 Source # | |
Defined in LawfulConversions.Relations.Int16AndWord16 | |
IsMany Int32 Word32 Source # | |
Defined in LawfulConversions.Relations.Int32AndWord32 | |
IsMany Int64 Word64 Source # | |
Defined in LawfulConversions.Relations.Int64AndWord64 | |
IsMany Int8 Word8 Source # | |
Defined in LawfulConversions.Relations.Int8AndWord8 | |
IsMany Word16 Int16 Source # | |
Defined in LawfulConversions.Relations.Int16AndWord16 | |
IsMany Word32 Int32 Source # | |
Defined in LawfulConversions.Relations.Int32AndWord32 | |
IsMany Word64 Int64 Source # | |
Defined in LawfulConversions.Relations.Int64AndWord64 | |
IsMany Word8 Int8 Source # | |
Defined in LawfulConversions.Relations.Int8AndWord8 | |
IsMany Builder ByteArray Source # | |
IsMany Builder ByteString Source # | |
IsMany Builder ByteString Source # | |
IsMany Builder ShortByteString Source # | |
IsMany ByteString ByteArray Source # | |
IsMany ByteString Builder Source # | |
IsMany ByteString ByteString Source # | |
IsMany ByteString ShortByteString Source # | |
IsMany ByteString Text Source # | Lenient UTF-8 decoding. |
IsMany ByteString Text Source # | Lenient UTF-8 decoding. |
IsMany ByteString String Source # | Lenient UTF-8 decoding. |
IsMany ByteString ByteArray Source # | |
IsMany ByteString Builder Source # | |
IsMany ByteString ByteString Source # | |
IsMany ByteString ShortByteString Source # | |
IsMany ByteString Text Source # | Lenient UTF-8 decoding. |
IsMany ByteString Text Source # | Lenient UTF-8 decoding. |
IsMany ByteString String Source # | Lenient UTF-8 decoding. |
IsMany ShortByteString ByteArray Source # | |
IsMany ShortByteString Builder Source # | |
IsMany ShortByteString ByteString Source # | |
IsMany ShortByteString ByteString Source # | |
IsMany Text Builder Source # | |
IsMany Text Text Source # | |
Defined in LawfulConversions.Relations.LazyTextAndText | |
IsMany Text StrictBuilder Source # | |
IsMany Builder Text Source # | |
IsMany Builder Text Source # | |
IsMany Builder StrictBuilder Source # | |
IsMany Text Text Source # | |
Defined in LawfulConversions.Relations.LazyTextAndText | |
IsMany Text Builder Source # | |
IsMany Text StrictBuilder Source # | |
IsMany StrictBuilder Text Source # | |
IsMany StrictBuilder Builder Source # | |
IsMany StrictBuilder Text Source # | |
IsMany String Text Source # | |
Defined in LawfulConversions.Relations.StringAndText | |
IsMany String Builder Source # | |
IsMany String Text Source # | |
IsMany String StrictBuilder Source # | |
IsMany Int Word Source # | |
Defined in LawfulConversions.Relations.IntAndWord | |
IsMany Word Int Source # | |
Defined in LawfulConversions.Relations.IntAndWord | |
IsMany a a Source # | |
Defined in LawfulConversions.Classes.IsMany | |
IsMany ByteArray [Word8] Source # | |
IsMany Builder [Word8] Source # | |
IsMany ByteString [Word8] Source # | |
IsMany ByteString [Word8] Source # | |
IsMany ShortByteString [Word8] Source # | |
IsMany IntSet (Set Int) Source # | |
IsMany b (ViaIsSome a b) Source # | |
Defined in LawfulConversions.Proxies.ViaIsSome | |
IsMany (Set Int) IntSet Source # | |
IsMany [Word8] ByteArray Source # | |
IsMany [Word8] Builder Source # | |
IsMany [Word8] ByteString Source # | |
IsMany [Word8] ByteString Source # | |
IsMany [Word8] ShortByteString Source # | |
IsMany (Seq a) (Vector a) Source # | |
IsMany (Seq a) [a] Source # | |
Defined in LawfulConversions.Relations.ListAndSeq | |
IsMany (Vector a) (Seq a) Source # | |
IsMany (Vector a) [a] Source # | |
IsMany [a] (Seq a) Source # | |
Defined in LawfulConversions.Relations.ListAndSeq | |
IsMany [a] (Vector a) Source # | |
IsMany (IntMap v) (Map Int v) Source # | |
IsMany (ViaIsSome a b) b Source # | |
Defined in LawfulConversions.Proxies.ViaIsSome | |
IsMany (Map Int v) (IntMap v) Source # | |
class (IsMany a b, Is b a) => Is a b Source #
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
from
is an inverse of to
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)
to
is an inverse 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)
Testing
For testing whether your instances conform to these laws use isProperties
.
Instance Definition
For each pair of isomorphic types (A and B) the compiler will require you to define six instances, namely: Is A B
and Is B A
, IsMany A B
and IsMany B A
, IsSome A B
and IsSome B A
.
Instances of Is
do not define any functions and serve merely as a statement that the laws are satisfied.
Example: Lazy Text and Text
instance IsSome Data.Text.Lazy.LazyText Data.Text.Text where to = LazyText.fromStrict
instance IsSome Data.Text.Text Data.Text.Lazy.LazyText where to = LazyText.toStrict
instance IsMany Data.Text.Lazy.LazyText Data.Text.Text instance IsMany Data.Text.Text Data.Text.Lazy.LazyText instance Is Data.Text.Lazy.LazyText Data.Text.Text instance Is Data.Text.Text Data.Text.Lazy.LazyText
Instances
Is ByteArray Builder Source # | |
Is ByteArray ByteString Source # | |
Is ByteArray ByteString Source # | |
Is ByteArray ShortByteString Source # | |
Is Int16 Word16 Source # | |
Defined in LawfulConversions.Relations.Int16AndWord16 | |
Is Int32 Word32 Source # | |
Defined in LawfulConversions.Relations.Int32AndWord32 | |
Is Int64 Word64 Source # | |
Defined in LawfulConversions.Relations.Int64AndWord64 | |
Is Int8 Word8 Source # | |
Defined in LawfulConversions.Relations.Int8AndWord8 | |
Is Word16 Int16 Source # | |
Defined in LawfulConversions.Relations.Int16AndWord16 | |
Is Word32 Int32 Source # | |
Defined in LawfulConversions.Relations.Int32AndWord32 | |
Is Word64 Int64 Source # | |
Defined in LawfulConversions.Relations.Int64AndWord64 | |
Is Word8 Int8 Source # | |
Defined in LawfulConversions.Relations.Int8AndWord8 | |
Is Builder ByteArray Source # | |
Is Builder ByteString Source # | |
Is Builder ByteString Source # | |
Is Builder ShortByteString Source # | |
Is ByteString ByteArray Source # | |
Is ByteString Builder Source # | |
Is ByteString ByteString Source # | |
Is ByteString ShortByteString Source # | |
Is ByteString ByteArray Source # | |
Is ByteString Builder Source # | |
Is ByteString ByteString Source # | |
Is ByteString ShortByteString Source # | |
Is ShortByteString ByteArray Source # | |
Is ShortByteString Builder Source # | |
Is ShortByteString ByteString Source # | |
Is ShortByteString ByteString Source # | |
Is Text Builder Source # | |
Is Text Text Source # | |
Defined in LawfulConversions.Relations.LazyTextAndText | |
Is Text StrictBuilder Source # | |
Is Builder Text Source # | |
Is Builder Text Source # | |
Is Builder StrictBuilder Source # | |
Is Text Text Source # | |
Defined in LawfulConversions.Relations.LazyTextAndText | |
Is Text Builder Source # | |
Is Text StrictBuilder Source # | |
Is StrictBuilder Text Source # | |
Is StrictBuilder Builder Source # | |
Is StrictBuilder Text Source # | |
Is Int Word Source # | |
Defined in LawfulConversions.Relations.IntAndWord | |
Is Word Int Source # | |
Defined in LawfulConversions.Relations.IntAndWord | |
Is a a Source # | Any type is isomorphic to itself. |
Defined in LawfulConversions.Classes.Is | |
Is ByteArray [Word8] Source # | |
Is Builder [Word8] Source # | |
Is ByteString [Word8] Source # | |
Is ByteString [Word8] Source # | |
Is ShortByteString [Word8] Source # | |
Is IntSet (Set Int) Source # | |
Is b (ViaIsSome a b) Source # | |
Defined in LawfulConversions.Proxies.ViaIsSome | |
Is (Set Int) IntSet Source # | |
Is [Word8] ByteArray Source # | |
Is [Word8] Builder Source # | |
Is [Word8] ByteString Source # | |
Is [Word8] ByteString Source # | |
Is [Word8] ShortByteString Source # | |
Is (Seq a) (Vector a) Source # | |
Is (Seq a) [a] Source # | |
Defined in LawfulConversions.Relations.ListAndSeq | |
Is (Vector a) (Seq a) Source # | |
Is (Vector a) [a] Source # | |
Is [a] (Seq a) Source # | |
Defined in LawfulConversions.Relations.ListAndSeq | |
Is [a] (Vector a) Source # | |
Is (IntMap v) (Map Int v) Source # | |
Is (ViaIsSome a b) b Source # | |
Defined in LawfulConversions.Proxies.ViaIsSome | |
Is (Map Int v) (IntMap v) Source # | |
Optics
isSomePrism :: (IsSome a b, Choice p, Applicative f) => p b (f b) -> p a (f a) Source #
Van-Laarhoven-style Prism, compatible with libraries like "lens" and "optics".
isManyIso :: (IsMany a b, Profunctor p, Functor f) => p b (f b) -> p a (f a) Source #
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) Source #
Van-Laarhoven-style Isomorphism, compatible with libraries like "lens" and "optics".
Instance derivation
Proxy data-types useful for deriving various standard instances using the DerivingVia
extension.
newtype ViaIsSome a b Source #
Helper for deriving common instances on types which have an instance of
using the IsSome
aDerivingVia
extension.
E.g.,
newtype Percent = Percent Double deriving newtype (Show, Eq, Ord) deriving (Read, 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
.
Constructors
ViaIsSome b |
Instances
Is b (ViaIsSome a b) Source # | |
Defined in LawfulConversions.Proxies.ViaIsSome | |
IsMany b (ViaIsSome a b) Source # | |
Defined in LawfulConversions.Proxies.ViaIsSome | |
IsSome a b => IsSome a (ViaIsSome a b) Source # | |
IsSome b (ViaIsSome a b) Source # | |
(IsSome a b, Arbitrary a) => Arbitrary (ViaIsSome a b) Source # | |
(IsSome a b, IsString a) => IsString (ViaIsSome a b) Source # | |
Defined in LawfulConversions.Proxies.ViaIsSome Methods fromString :: String -> ViaIsSome a b | |
(IsSome a b, Read a) => Read (ViaIsSome a b) Source # | |
Defined in LawfulConversions.Proxies.ViaIsSome | |
(IsSome a b, Show a) => Show (ViaIsSome a b) Source # | |
(IsSome a b, Eq a) => Eq (ViaIsSome a b) Source # | |
(IsSome a b, Ord a) => Ord (ViaIsSome a b) Source # | |
Defined in LawfulConversions.Proxies.ViaIsSome | |
Is (ViaIsSome a b) b Source # | |
Defined in LawfulConversions.Proxies.ViaIsSome | |
IsMany (ViaIsSome a b) b Source # | |
Defined in LawfulConversions.Proxies.ViaIsSome | |
IsSome (ViaIsSome a b) b Source # | |
Testing
isSomeProperties :: (IsSome a b, Eq a, Eq b, Show a, Show b, Arbitrary b) => Proxy a -> Proxy b -> [(String, Property)] Source #
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) (isSomeProperties @Int32 @Int16 Proxy Proxy)
isManyProperties :: (IsMany a b, Eq a, Eq b, Show a, Show b, Arbitrary b) => Proxy a -> Proxy b -> [(String, Property)] Source #
Properties testing whether an instance satisfies the laws of IsMany
.
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 "IsMany laws" do traverse_ (uncurry prop) (isManyProperties @String @Text Proxy Proxy)
isProperties :: (Is a b, Eq a, Eq b, Show a, Show b, Arbitrary a, Arbitrary b) => Proxy a -> Proxy b -> [(String, Property)] Source #
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) (isProperties @Int32 @Word32 Proxy Proxy)