Search code examples
haskelltypesconstructor

Haskell type of specific data constructor


Suppose I have the following Haskell code:

data Option
    = Help
    | Opt1 Int Double String
    -- more options would be here in a real case

handleOption :: Option -> IO ()
handleOption option = case option of
    Help -> handleHelp
    Opt1 n f s -> handleOpt1 n f s

handleHelp :: IO ()
handleHelp = print "help"

handleOpt1 :: Int -> Double -> String -> IO ()
handleOpt1 n f s = print (n, f, s)

In the above code, it seems to me a waste to deconstruct the object ahead of time in the sense that I could keep the data bundled neatly together. Now I have to pass each part of Opt1 individually or create a single separate data type to haul them along. Is it possible to pass in the entire Opt1 to handleOpt1 while not allowing a general Option instance being passed in, such as making handleOpt1 Help a compile error?

Example pseudo code below:


data Option
    = Help
    | Opt1 Int Double String

handleOption :: Option -> IO ()
handleOption option = case option of
    Help -> handleHelp
    opt1 @ Opt1{} -> handleOpt1 opt1

handleHelp :: IO ()
handleHelp = print "help"

handleOpt1 :: Option:Opt1 -> IO ()
handleOpt1 (Opt1 n f s) = print (n, f, s)

Solution

  • You can use GADTs for this.

    {-# LANGUAGE GADTs #-}
    
    data Option a where
        Help :: Option ()
        Opt1 :: Int -> Double -> String -> Option (Int, Double, String)
    
    handleOption :: Option a -> IO ()
    handleOption option = case option of
        Help          -> handleHelp
        opt1 @ Opt1{} -> handleOpt1 opt1
    
    handleHelp :: IO ()
    handleHelp = print "help"
    
    handleOpt1 :: Option (Int, Double, String) -> IO ()
    handleOpt1 (Opt1 n f s) = print (n, f, s)
    

    With GADTs, you give more type information to the compiler. For handleOpt1, since it only accepts Option (Int, Double, String), the compiler knows Option () (i.e. Help) will never be passed in.

    That said, using GADTs makes quite a few other things harder. For instance, automatic deriving (e.g. deriving (Eq, Show)) generally doesn't work with them. You should carefully consider the pros and cons of using them in your case.