Copyright | Dennis Gosnell 2017 |
---|---|
License | BSD3 |
Maintainer | Dennis Gosnell (cdep.illabout@gmail.com) |
Stability | experimental |
Portability | unknown |
Safe Haskell | None |
Language | Haskell2010 |
This package defines a type called OpenUnion
. This represents an open union
of possible types (also called an open sum type).
Here is an example of taking a String
, and lifting it up into an open union
of a String
and Int
:
let int = 3 ::Int
let o =openUnionLift
int ::OpenUnion
'[String
,Int
]
There are a couple different ways to pattern match on a OpenUnion
.
The easiest one is to use catchesOpenUnion
, which takes a tuple of handlers for
each possible type in the OpenUnion
:
let strHandler = (str -> "got a String: "++
str) ::String
->String
intHandler = (int -> "got an Int: "++
show
int) ::Int
->String
incatchesOpenUnion
(strHandler, intHandler) u ::String
The above will print got an Int: 3
.
There is also the openUnionMatch
function, as well as fromOpenUnion
and
openUnion
. Read the documentation below for more information.
- 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
- type IsMember (a :: u) (as :: [u]) = UElem a as (RIndex a as)
- data Union (f :: u -> *) (as :: [u]) where
- union :: (Union f as -> c) -> (f a -> c) -> Union f (a ': as) -> c
- absurdUnion :: Union f '[] -> a
- umap :: (forall a. f a -> g a) -> Union f as -> Union g as
- catchesUnion :: (Applicative f, ToProduct tuple f (ReturnX x as)) => tuple -> Union f as -> f x
- _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 ...
- class i ~ RIndex a as => UElem (a :: u) (as :: [u]) (i :: Nat) where
- type OpenProduct = Product Identity
- data Product (f :: u -> *) (as :: [u]) where
- class ToOpenProduct (tuple :: *) (as :: [*]) | as -> tuple
- tupleToOpenProduct :: ToOpenProduct t as => t -> OpenProduct as
- class ToProduct (tuple :: *) (f :: u -> *) (as :: [u]) | f as -> tuple
- tupleToProduct :: ToProduct t f as => t -> Product f as
- type family ReturnX x as where ...
OpenUnion
OpenUnion
Helpers
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"
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
.
Union
(used by OpenUnion
)
OpenUnion
is a type synonym around Union
. Most users will be able to
work directly with OpenUnion
and ignore this Union
type.
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 helpers
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"
absurdUnion :: Union f '[] -> a Source #
Since a union with an empty list of labels is uninhabited, we can recover any type from it.
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.
Union 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 used with Union
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
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
OpenProduct
This OpenProduct
type is used to easily create a case-analysis for
Union
s. You can see it being used in catchesOpenUnion
and
The ToProduct
type class makes it easy to convert a
tuple to a Product
. This class is used so that the end user only has to worry
about working with tuples, and can mostly ignore this Product
type.
type OpenProduct = Product Identity Source #
data Product (f :: u -> *) (as :: [u]) where Source #
An extensible product type. This is similar to
Union
, except a product type
instead of a sum type.
class ToOpenProduct (tuple :: *) (as :: [*]) | as -> tuple Source #
ToOpenProduct
gives us a way to convert a tuple to an OpenProduct
.
See tupleToOpenProduct
.
ToOpenProduct a ((:) * a ([] *)) Source # | Convert a single value into an |
ToOpenProduct (a, b) ((:) * a ((:) * b ([] *))) Source # | Convert a tuple into an |
ToOpenProduct (a, b, c) ((:) * a ((:) * b ((:) * c ([] *)))) Source # | Convert a 3-tuple into an |
ToOpenProduct (a, b, c, d) ((:) * a ((:) * b ((:) * c ((:) * d ([] *))))) Source # | Convert a 4-tuple into an |
tupleToOpenProduct :: ToOpenProduct t as => t -> OpenProduct as Source #
Turn a tuple into an OpenProduct
.
Examples
Turn a triple into an OpenProduct
:
>>>
tupleToOpenProduct (1, 2.0, "hello") :: OpenProduct '[Int, Double, String]
Cons (Identity 1) (Cons (Identity 2.0) (Cons (Identity "hello") Nil))
Turn a single value into an OpenProduct
:
>>>
tupleToOpenProduct 'c' :: OpenProduct '[Char]
Cons (Identity 'c') Nil
class ToProduct (tuple :: *) (f :: u -> *) (as :: [u]) | f as -> tuple Source #
This type class provides a way to turn a tuple into a Product
.
ToProduct u (f a) f ((:) u a ([] u)) Source # | Convert a single value into a |
ToProduct u (f a, f b) f ((:) u a ((:) u b ([] u))) Source # | Convert a tuple into a |
ToProduct u (f a, f b, f c) f ((:) u a ((:) u b ((:) u c ([] u)))) Source # | Convert a 3-tuple into a |
ToProduct u (f a, f b, f c, f d) f ((:) u a ((:) u b ((:) u c ((:) u d ([] u))))) Source # | Convert a 4-tuple into a |
tupleToProduct :: ToProduct t f as => t -> Product f as Source #
Turn a tuple into a Product
.
>>>
tupleToProduct (Identity 1, Identity 2.0) :: Product Identity '[Int, Double]
Cons (Identity 1) (Cons (Identity 2.0) Nil)
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