Copyright | Dennis Gosnell 2017 |
---|---|
License | BSD3 |
Maintainer | Dennis Gosnell (cdep.illabout@gmail.com) |
Stability | experimental |
Portability | unknown |
Safe Haskell | None |
Language | Haskell2010 |
- data Union (f :: u -> *) (as :: [u]) where
- union :: (Union f as -> c) -> (f a -> c) -> Union f (a ': as) -> c
- catchesUnion :: (Applicative f, ToProduct tuple f (ReturnX x as)) => tuple -> Union f as -> f x
- absurdUnion :: Union f '[] -> a
- umap :: (forall a. f a -> g a) -> Union f as -> Union g as
- _This :: Prism (Union f (a ': as)) (Union f (b ': as)) (f a) (f b)
- _That :: Prism (Union f (a ': as)) (Union f (a ': bs)) (Union f as) (Union f bs)
- data Nat
- type family RIndex (r :: k) (rs :: [k]) :: Nat where ...
- type family ReturnX x as where ...
- class i ~ RIndex a as => UElem (a :: u) (as :: [u]) (i :: Nat) where
- type IsMember (a :: u) (as :: [u]) = UElem a as (RIndex a as)
- type OpenUnion = Union Identity
- openUnion :: (OpenUnion as -> c) -> (a -> c) -> OpenUnion (a ': as) -> c
- fromOpenUnion :: (OpenUnion as -> a) -> OpenUnion (a ': as) -> a
- fromOpenUnionOr :: OpenUnion (a ': as) -> (OpenUnion as -> a) -> a
- openUnionPrism :: forall a as. IsMember a as => Prism' (OpenUnion as) a
- openUnionLift :: forall a as. IsMember a as => a -> OpenUnion as
- openUnionMatch :: forall a as. IsMember a as => OpenUnion as -> Maybe a
- catchesOpenUnion :: ToOpenProduct tuple (ReturnX x as) => tuple -> OpenUnion as -> x
Union
data Union (f :: u -> *) (as :: [u]) where Source #
A Union
is parameterized by a universe u
, an interpretation f
and a list of labels as
. The labels of the union are given by
inhabitants of the kind u
; the type of values at any label a ::
u
is given by its interpretation f a :: *
.
What does this mean in practice? It means that a type like
can be _either_ an
Union
Identity
'[String
, Int
]
or an Identity
String
.Identity
Int
You need to pattern match on the This
and That
constructors to figure
out whether you are holding a String
or Int
:
>>>
let u = That (This (Identity 1)) :: Union Identity '[String, Int]
>>>
:{
case u of This (Identity str) -> "we got a string: " ++ str That (This (Identity int)) -> "we got an int: " ++ show int :} "we got an int: 1"
There are multiple functions that let you perform this pattern matching
easier: union
, catchesUnion
, unionMatch
There is also a type synonym OpenUnion
for the common case of
, as well as helper functions for working with it.Union
Indentity
(Eq (f a2), Eq (Union a1 f as)) => Eq (Union a1 f ((:) a1 a2 as)) Source # | |
Eq (Union u f ([] u)) Source # | |
(Ord (f a2), Ord (Union a1 f as)) => Ord (Union a1 f ((:) a1 a2 as)) Source # | |
Ord (Union u f ([] u)) Source # | |
(Read (f a2), Read (Union a1 f as)) => Read (Union a1 f ((:) a1 a2 as)) Source # | This is only a valid instance when the For instance, imagine we are working with a
However, imagine are we working with a
If the order of the types is flipped around, we are are able to read
|
Read (Union u f ([] u)) Source # | This will always fail, since |
(Show (f a2), Show (Union a1 f as)) => Show (Union a1 f ((:) a1 a2 as)) Source # | |
Show (Union u f ([] u)) Source # | |
(ToJSON (f a2), ToJSON (Union a1 f as)) => ToJSON (Union a1 f ((:) a1 a2 as)) Source # | |
ToJSON (Union u f ([] u)) Source # | |
(FromJSON (f a2), FromJSON (Union a1 f as)) => FromJSON (Union a1 f ((:) a1 a2 as)) Source # | This is only a valid instance when the This is similar to the |
FromJSON (Union u f ([] u)) Source # | This will always fail, since |
(NFData (f a2), NFData (Union a1 f as)) => NFData (Union a1 f ((:) a1 a2 as)) Source # | |
NFData (Union u f ([] u)) Source # | |
union :: (Union f as -> c) -> (f a -> c) -> Union f (a ': as) -> c Source #
Case analysis for Union
.
Examples
Here is an example of matching on a This
:
>>>
let u = This (Identity "hello") :: Union Identity '[String, Int]
>>>
let runIdent = runIdentity :: Identity String -> String
>>>
union (const "not a String") runIdent u
"hello"
Here is an example of matching on a That
:
>>>
let v = That (This (Identity 3.3)) :: Union Identity '[String, Double, Int]
>>>
union (const "not a String") runIdent v
"not a String"
catchesUnion :: (Applicative f, ToProduct tuple f (ReturnX x as)) => tuple -> Union f as -> f x Source #
An alternate case anaylsis for a Union
. This method uses a tuple
containing handlers for each potential value of the Union
. This is
somewhat similar to the catches
function.
Examples
Here is an example of handling a Union
with two possible values. Notice
that a normal tuple is used:
>>>
let u = This $ Identity 3 :: Union Identity '[Int, String]
>>>
let intHandler = (Identity $ \int -> show int) :: Identity (Int -> String)
>>>
let strHandler = (Identity $ \str -> str) :: Identity (String -> String)
>>>
catchesUnion (intHandler, strHandler) u :: Identity String
Identity "3"
Given a Union
like
, the type of
Union
Identity
'[Int
, String
]catchesUnion
becomes the following:
catchesUnion
:: (Identity
(Int
->String
),Identity
(String
->String
)) ->Union
Identity
'[Int
,String
] ->Identity
String
Checkout catchesOpenUnion
for more examples.
absurdUnion :: Union f '[] -> a Source #
Since a union with an empty list of labels is uninhabited, we can recover any type from it.
Optics
_This :: Prism (Union f (a ': as)) (Union f (b ': as)) (f a) (f b) Source #
Lens-compatible Prism
for This
.
Examples
Use _This
to construct a Union
:
>>>
review _This (Just "hello") :: Union Maybe '[String]
Just "hello"
Use _This
to try to destruct a Union
into a f a
:
>>>
let u = This (Identity "hello") :: Union Identity '[String, Int]
>>>
preview _This u :: Maybe (Identity String)
Just (Identity "hello")
Use _This
to try to destruct a Union
into a f a
(unsuccessfully):
>>>
let v = That (This (Identity 3.3)) :: Union Identity '[String, Double, Int]
>>>
preview _This v :: Maybe (Identity String)
Nothing
_That :: Prism (Union f (a ': as)) (Union f (a ': bs)) (Union f as) (Union f bs) Source #
Lens-compatible Prism
for That
.
Examples
Use _That
to construct a Union
:
>>>
let u = This (Just "hello") :: Union Maybe '[String]
>>>
review _That u :: Union Maybe '[Double, String]
Just "hello"
Use _That
to try to peel off a That
from a Union
:
>>>
let v = That (This (Identity "hello")) :: Union Identity '[Int, String]
>>>
preview _That v :: Maybe (Union Identity '[String])
Just (Identity "hello")
Use _That
to try to peel off a That
from a Union
(unsuccessfully):
>>>
let w = This (Identity 3.5) :: Union Identity '[Double, String]
>>>
preview _That w :: Maybe (Union Identity '[String])
Nothing
Typeclasses
A mere approximation of the natural numbers. And their image as lifted by
-XDataKinds
corresponds to the actual natural numbers.
type family RIndex (r :: k) (rs :: [k]) :: Nat where ... Source #
A partial relation that gives the index of a value in a list.
Examples
Find the first item:
>>>
import Data.Type.Equality ((:~:)(Refl))
>>>
Refl :: RIndex String '[String, Int] :~: 'Z
Refl
Find the third item:
>>>
Refl :: RIndex Char '[String, Int, Char] :~: 'S ('S 'Z)
Refl
type family ReturnX x as where ... Source #
Change a list of types into a list of functions that take the given type
and return x
.
>>>
import Data.Type.Equality ((:~:)(Refl))
>>>
Refl :: ReturnX Double '[String, Int] :~: '[String -> Double, Int -> Double]
Refl
Don't do anything with an empty list:
>>>
Refl :: ReturnX Double '[] :~: '[]
Refl
class i ~ RIndex a as => UElem (a :: u) (as :: [u]) (i :: Nat) where Source #
provides a way to potentially get an UElem
a as if a
out of a
(Union
f asunionMatch
). It also provides a way to create a
from an Union
f asf a
(unionLift
).
This is safe because of the RIndex
contraint. This RIndex
constraint
tells us that there actually is an a
in as
at index i
.
As an end-user, you should never need to implement an additional instance of this typeclass.
unionPrism :: Prism' (Union f as) (f a) Source #
This is implemented as
.prism'
unionLift
unionMatch
unionLift :: f a -> Union f as Source #
This is implemented as
.review
unionPrism
unionMatch :: Union f as -> Maybe (f a) Source #
This is implemented as
.preview
unionPrism
type IsMember (a :: u) (as :: [u]) = UElem a as (RIndex a as) Source #
This is a helpful Constraint
synonym to assert that a
is a member of
as
. You can see how it is used in functions like openUnionLift
.
OpenUnion
openUnion :: (OpenUnion as -> c) -> (a -> c) -> OpenUnion (a ': as) -> c Source #
Case analysis for OpenUnion
.
Examples
Here is an example of successfully matching:
>>>
let string = "hello" :: String
>>>
let o = openUnionLift string :: OpenUnion '[String, Int]
>>>
openUnion (const "not a String") id o
"hello"
Here is an example of unsuccessfully matching:
>>>
let double = 3.3 :: Double
>>>
let p = openUnionLift double :: OpenUnion '[String, Double, Int]
>>>
openUnion (const "not a String") id p
"not a String"
fromOpenUnion :: (OpenUnion as -> a) -> OpenUnion (a ': as) -> a Source #
This is similar to fromMaybe
for an OpenUnion
.
Examples
Here is an example of successfully matching:
>>>
let string = "hello" :: String
>>>
let o = openUnionLift string :: OpenUnion '[String, Int]
>>>
fromOpenUnion (const "not a String") o
"hello"
Here is an example of unsuccessfully matching:
>>>
let double = 3.3 :: Double
>>>
let p = openUnionLift double :: OpenUnion '[String, Double, Int]
>>>
fromOpenUnion (const "not a String") p
"not a String"
fromOpenUnionOr :: OpenUnion (a ': as) -> (OpenUnion as -> a) -> a Source #
Flipped version of fromOpenUnion
.
openUnionPrism :: forall a as. IsMember a as => Prism' (OpenUnion as) a Source #
Just like unionPrism
but for OpenUnion
.
openUnionLift :: forall a as. IsMember a as => a -> OpenUnion as Source #
openUnionMatch :: forall a as. IsMember a as => OpenUnion as -> Maybe a Source #
Just like unionMatch
but for OpenUnion
.
Examples
Successful matching:
>>>
let string = "hello" :: String
>>>
let o = openUnionLift string :: OpenUnion '[Double, String, Int]
>>>
openUnionMatch o :: Maybe String
Just "hello"
Failure matching:
>>>
let double = 3.3 :: Double
>>>
let p = openUnionLift double :: OpenUnion '[Double, String]
>>>
openUnionMatch p :: Maybe String
Nothing
catchesOpenUnion :: ToOpenProduct tuple (ReturnX x as) => tuple -> OpenUnion as -> x Source #
An alternate case anaylsis for an OpenUnion
. This method uses a tuple
containing handlers for each potential value of the OpenUnion
. This is
somewhat similar to the catches
function.
When working with large OpenUnion
s, it can be easier to use
catchesOpenUnion
than openUnion
.
Examples
Here is an example of handling an OpenUnion
with two possible values.
Notice that a normal tuple is used:
>>>
let u = openUnionLift (3 :: Int) :: OpenUnion '[Int, String]
>>>
let intHandler = (\int -> show int) :: Int -> String
>>>
let strHandler = (\str -> str) :: String -> String
>>>
catchesOpenUnion (intHandler, strHandler) u :: String
"3"
Given an OpenUnion
like
, the type of
OpenUnion
'[Int
, String
]catchesOpenUnion
becomes the following:
catchesOpenUnion
:: (Int
-> x,String
-> x) ->OpenUnion
'[Int
,String
] -> x
Here is an example of handling an OpenUnion
with three possible values:
>>>
let u = openUnionLift ("hello" :: String) :: OpenUnion '[Int, String, Double]
>>>
let intHandler = (\int -> show int) :: Int -> String
>>>
let strHandler = (\str -> str) :: String -> String
>>>
let dblHandler = (\dbl -> "got a double") :: Double -> String
>>>
catchesOpenUnion (intHandler, strHandler, dblHandler) u :: String
"hello"
Here is an example of handling an OpenUnion
with only one possible value.
Notice how a tuple is not used, just a single value:
>>>
let u = openUnionLift (2.2 :: Double) :: OpenUnion '[Double]
>>>
let dblHandler = (\dbl -> "got a double") :: Double -> String
>>>
catchesOpenUnion dblHandler u :: String
"got a double"
Setup code for doctests
>>>
:set -XDataKinds
>>>
:set -XGADTs
>>>
:set -XTypeOperators
>>>
import Data.Text (Text)
>>>
import Text.Read (readMaybe)