假设我有以下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))通常不适用于它们。您应该仔细考虑在您的情况下使用它们的利弊。

10-08 07:02
查看更多