I have created a very simple example of a problem I'm having using GADTs and DataKinds. My real application is obviously more complicated but this captures the essence of my situation clearly. I'm trying to create a function that can return any of the values (T1, T2) of type Test. Is there a way to accomplish this or am I getting into the realm of dependent types? The questions here seem similar but I could not find (or comprehend) an answer to my question from them. I'm just starting to understand these GHC extensions. Thanks.
{-# LANGUAGE GADTs, DataKinds, FlexibleInstances, KindSignatures #-}
module Test where
data TIdx = TI | TD
data Test :: TIdx -> * where
T1 :: Int -> Test TI
T2 :: Double -> Test TD
type T1 = Test TI
type T2 = Test TD
prob :: T1 -> T2 -> Test TIdx
prob x y = undefined
----Here is the error---- Test.hs:14:26:
Kind mis-match
The first argument of `Test' should have kind `TIdx',
but `TIdx' has kind `*'
In the type signature for `prob': prob :: T1 -> T2 -> Test TIdx
The error message you get is because the type parameter to
Test
needs to have the kindTIdx
, but the only types that have that kind areTI
andTD
. The typeTIdx
has the kind*
.If I understood correctly what you are trying to express is that the result type of
prob
is eitherTest TI
orTest TD
, but the actual type is determined at runtime. However, this won't work directly. The return type generally has to be known at compile time.What you can do, since the GADT constructors each map to specific phatom type of kind
TIdx
, is to return a result that erases the phantom type with an existential or another GADT and then recover the type later using a pattern match.For example, if we define two functions that require a specific kind of
Test
:This type-checks:
The notable thing here is that in the
case
-expression, even though theUnknownTest
GADT has erased the phantom type, theT1
andT2
constructors give enough type information to the compiler thatt
recovers its exact typeTest TI
orTest TD
within the branch of the case-expression, allowing us to e.g. call functions that expect those specific types.You have two options here. Either you can infer the type of the return value from the types of arguments or you can't.
In the former case, you refine the type:
In the latter case, you have to erase the type information:
You can also erase the type information by using an existential type:
In this case, you cannot do anything interesting with a value of
SomeTest
, but you might be able in your real example.