Search code examples
haskellgadtdata-kinds

Trouble with DataKinds


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.

similar question 1

similar question 2

{-# 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

Solution

  • 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.