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