I have a project where I define a "heterogenous list" like the following. I'm not sure what this trick is called but I've found it to be useful:
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeOperators #-}
import Data.Kind (Type)
data (a :: Type) :> (b :: Type) = a :> b
deriving Show
infixr :>
With this, I can write arbitrarily long lists of values like this:
val :: Int :> Bool :> String
val = 42 :> True :> "asdf"
I'm trying to write a function to test if such a value contains a given type, and if so extract it as a Maybe. For example,
-- | getInt (42 :> "asdf") -> Just 42
-- | getInt (True :> "asdf") -> Nothing
getInt :: a :> b -> Maybe Int
getInt _ = ???
But I haven't figured out a way to write functions like this. I tried using
cast from Data.Typeable, but ran into various problems trying to destructure at the type level.
You can do this with
Type.Reflection, as per @chi's comment (see below).However, you might find it easier to use a more traditional list type with both a "cons" and a "nil" constructor and embedded
Typeableinstances:Note that you can maintain most of the ergonomics of the original with a few cleverly named type families and aliases:
This allows you to write:
because the
:>andNilin types are type families/aliases which expand to a validHListtype, and the:>andNilin expressions are the constructors for the correspondingHListtype.With this in place, it's easy to define
getIntwith justcastfromData.Typeable:Critically, the extra
Nilconstructor eliminates the need to try to distinguish at the type level if the right-hand side of a:>constructor is a final value or "more list".This will generally be much more pleasant to work with in all sorts of ways at the relatively small cost of a little
:> Nilboilerplate.A full example:
which produces the output:
However, if you want to stick with your
:>-only approach, thenData.Typeableis insufficient because it can't be "decomposed" at the type level. You've probably observed that it's easy enough to write:which can successfully extract the
IntfromInt :> BoolandBool :> Int, but there's no straightforward way to recurse usingcast:Because even though the
cast bcan successfully castbto ac :> d, there's no way to get the requiredTypeable candTypeable dconstraints fromTypeable b.However, if you use
Type.Reflectioninstead, it's possible. Note that I've used explicit export lists here, but basically I've usedcastfromData.Typeablefor convenience, and everything else (including theTypeabletype itself) needs to come from `Type.Reflection).The full example: