5. Converting variants¶
5.1. Converting from/to a value¶
We can easily convert between a variant with a single value type and this value
type with variantToValue
and variantFromValue
:
intV :: V '[Int]
intV = V @Int 10
> variantToValue intV
10
> :t variantToValue intV
variantToValue intV :: Int
> :t variantFromValue "Test"
variantFromValue "Test" :: V '[String]
variantFromValue
is especially useful to avoid having to define the value
types of the variant explicitly.
5.2. Converting from/to Either¶
variantFromEither
and variantToEither
can be used to convert between a
variant of arity 2 and the Either
data type:
eith :: Either Int String
eith = Left 10
> :t variantFromEither eith
variantFromEither eith :: V '[String, Int]
x,y :: V '[String,Int]
x = V "test"
y = V @Int 10
> variantToEither x
Right "test"
> variantToEither y
Left 10
5.3. Extending¶
We can extend the value types of a variant by appending or prepending a list of
types with appendVariant
and prependVariant
:
x :: V '[String,Int]
x = V "test"
data A = A
data B = B
px = prependVariant @'[A,B] x
ax = appendVariant @'[A,B] x
> :t ax
ax :: V '[String, Int, A, B]
> :t px
px :: V '[A, B, String, Int]
You can use the Concat
type family to specify the type of a concatened
variant:
data Error0 = Error0 deriving Show
data Error1 = Error1 deriving Show
checkErr ::
( Int :< is
, os ~ Concat is '[Error0,Error1]
, Error0 :< os
, Error1 :< os
) => V is -> V os
checkErr = \case
V (0 :: Int) -> V Error0
V (1 :: Int) -> V Error1
v -> appendVariant @'[Error0,Error1] v
> checkErr (V @Int 0 :: V '[Float,Int])
V @Error0 Error0
> checkErr (V @Int 1 :: V '[Float,Int])
V @Error1 Error1
> checkErr (V @Int 2 :: V '[Float,Int])
V @Int 2
> checkErr (V @Float 5.0 :: V '[Float,Int])
V @Float 5.0
> z = checkErr (V @Float 5.0 :: V '[Float,Int,String,Double])
> :t z
z :: V '[Float, Int, [Char], Double, Error0, Error1]
Appending and prepending are very cheap operations: appending just messes with types and performs nothing at runtime; prepending only increases the tag value at runtime by a constant number.
5.4. Extending and reordering: lifting¶
We can extend and reorder the value types of a variant with the liftVariant
function:
x :: V '[String,Int]
x = V "test"
-- adding Double and Float, and reordering
y :: V '[Double,Int,Float,String]
y = liftVariant x
You can use the LiftVariant is os
constraint to write generic code and to
ensure that the type list is
is a subset of os
:
liftX :: (LiftVariant is (Double : Float : is))
=> V is -> V (Double : Float : is)
liftX = liftVariant
> :t liftX x
liftX x :: V '[Double, Float, String, Int]
> :t liftX (V "test" :: V [String])
liftX (V "test" :: V [String]) :: V [Double, Float, String]
5.5. Nubing¶
If the list of value types of a variant contains the same type more than once,
we can decide to only keep one of them with nubVariant
:
> z = nubVariant (V "test" :: V '[String,Int,Double,Float,Double,String])
> :t z
z :: V '[String, Int, Double, Float]
You can use the Nub
type family to write generic code.
5.6. Flattening¶
If the value types of a variant are themselves variants, you can flatten them
with flattenVariant
:
x :: V '[String,Int]
x = V "test"
nest :: V '[ V '[String,Int], V '[Float,Double]]
nest = V x
> :t flattenVariant nest
flattenVariant nest :: V '[String, Int, Float, Double]
You can use the Flattenable
type-class and the FlattenVariant
type
family to write generic code.
5.7. Joining¶
We can transform a variant of functor values (e.g., V '[m a, m b, m c]
) into
a single functor value (e.g., m (V '[a,b,c])
) with joinVariant
:
fs0,fs1,fs2 :: V '[ Maybe Int, Maybe String, Maybe Double]
fs0 = V @(Maybe Int) (Just 10)
fs1 = V (Just "Test")
fs2 = V @(Maybe Double) Nothing
> joinVariant @Maybe fs0
Just (V @Int 10)
> joinVariant @Maybe fs1
Just (V @[Char] "Test")
> joinVariant @Maybe fs2
Nothing
It also works with IO
for example:
printRet :: Show a => a -> IO a
printRet a = do
print a
return a
ms0,ms1 :: V '[ IO Int, IO String, IO Double]
ms0 = V @(IO Int) (printRet 10)
ms1 = V (printRet "Test")
> joinVariant @IO ms0
10
V @Int 10
> joinVariant @IO ms1
"Test"
V @[Char] "Test"
> :t joinVariant @IO ms0
joinVariant @IO ms0 :: IO (V '[Int, String, Double])
Writing generic code requires the use of the JoinVariant m xs
constraint and
the resulting list of value types can be obtained with the ExtractM m xs
type family.
> :t joinVariant
joinVariant :: JoinVariant m xs => V xs -> m (V (ExtractM m xs))
Note
With IO
it is possible to use the joinVariantUnsafe
function which
doesn’t require the type application and doesn’t use the JoinVariant
type-class. However some other functor types aren’t supported (e.g.,
Maybe
) and using joinVariantUnsafe
with them makes the program crash
at runtime.
5.8. Combining two variants (product)¶
We can combine two variants into a single variant containing a tuple with
productVariant
:
fl :: V '[Float,Double]
fl = V @Float 5.0
d :: V '[Int,Word]
d = V @Word 10
dfl = productVariant d fl
> dfl
V @(Word,Float) (10,5.0)
> :t dfl
dfl :: V '[(Int, Float), (Int, Double), (Word, Float), (Word, Double)]
5.9. Converting to tuple/HList¶
We can convert a Variant into a tuple of Maybes with variantToTuple
:
w,k,u :: V '[String,Int,Double,Maybe Int]
w = V @Double 1.0
k = V (Just @Int 10)
u = V @Int 17
> :t variantToTuple w
variantToTuple w :: (Maybe String, Maybe Int, Maybe Double, Maybe (Maybe Int))
> variantToTuple w
(Nothing,Nothing,Just 1.0,Nothing)
> variantToTuple k
(Nothing,Nothing,Nothing,Just (Just 10))
> variantToTuple u
(Nothing,Just 17,Nothing,Nothing)
And similarly into an HList (heterogeneous list) with variantToHList
:
> variantToHList w
H[Nothing,Nothing,Just 1.0,Nothing]
> variantToHList k
H[Nothing,Nothing,Nothing,Just (Just 10)]
> variantToHList u
H[Nothing,Just 17,Nothing,Nothing]