假设我有以下Haskell代码:
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)
在上面的代码中,在我可以将数据整齐地捆绑在一起的意义上,提前解构对象似乎是浪费。现在,我必须分别传递Opt1的每个部分,或创建一个单独的数据类型来传递它们。是否可以将整个
Opt1
传递给handleOpt1
,同时不允许传递一般的Option
实例,例如使handleOpt1 Help
成为编译错误?下面的示例伪代码:
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)
最佳答案
您可以为此使用GADTs。
{-# 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)
使用GADT,您可以向编译器提供更多类型信息。对于
handleOpt1
,由于它仅接受Option (Int, Double, String)
,因此编译器知道Option ()
(即Help
)将永远不会传入。也就是说,使用GADT会使其他很多事情变得更加困难。例如,自动推导(例如
deriving (Eq, Show)
)通常不适用于它们。您应该仔细考虑在您的情况下使用它们的利弊。