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 si
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:
data Which :: TIdx -> * where
Fst :: Which TI
Snd :: Which TD
prob :: Which i -> T1 -> T2 -> Test i
prob Fst x y = x
prob Snd x y = y
In the latter case, you have to erase the type information:
prob :: Bool -> T1 -> T2 -> Either Int Double
prob True (T1 x) y = Left x
prob False x (T2 y) = Right y
You can also erase the type information by using an existential type:
data SomeTest = forall i . SomeTest (Test i)
prob :: Bool -> T1 -> T2 -> SomeTest
prob True x y = SomeTest x
prob False x y = SomeTest y
In this case, you cannot do anything interesting with a value of SomeTest
, but you might be able in your real example.
The error message you get is because the type parameter to Test
needs to
have the kind TIdx
, but the only types that have that kind are TI
and TD
.
The type TIdx
has the kind *
.
If I understood correctly what you are trying to express is that the result
type of prob
is either Test TI
or Test 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
:
fun1 :: T1 -> IO ()
fun1 (T1 i) = putStrLn $ "T1 " ++ show i
fun2 :: T2 -> IO ()
fun2 (T2 d) = putStrLn $ "T2 " ++ show d
This type-checks:
data UnknownTest where
UnknownTest :: Test t -> UnknownTest
prob :: T1 -> T2 -> UnknownTest
prob x y = undefined
main :: IO ()
main = do
let a = T1 5
b = T2 10.0
p = prob a b
case p of
UnknownTest t@(T1 _) -> fun1 t
UnknownTest t@(T2 _) -> fun2 t
The notable thing here is that in the case
-expression, even though the
UnknownTest
GADT has erased the phantom type, the T1
and T2
constructors give enough
type information to the compiler that t
recovers its exact type Test TI
or
Test TD
within the branch of the case-expression, allowing us to e.g. call
functions that expect those specific types.