3

假设我有以下 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 的每个部分,或者创建一个单独的数据类型来将它们拖走。是否可以传入整个Opt1tohandleOpt1而不允许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)
4

4 回答 4

6

您可以为此使用GADT

{-# 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 ()(ie Help) 永远不会被传入。

也就是说,使用 GADT 会使其他一些事情变得更加困难。例如,自动派生(例如deriving (Eq, Show))通常不适用于它们。您应该仔细考虑在您的案例中使用它们的利弊。

于 2013-04-19T22:25:29.933 回答
2

handleHelp在这个特定的例子中,通过抛弃并使handleOpt1它们成为函数的独立方程来解决“问题”似乎更自然handleOption

handleOption :: Option -> IO ()

handleOption Help = print "help"

handleOption (Opt1 n f s) = print (n, f, s)

这让你两全其美。您可以为每种情况编写一个单独的方程式(因此,即使每种情况都很大,您也可以防止它们融合成一个巨大的方程式),您不必编写任何样板“调度”函数,也不必命名Opt1外壳的各个部分,直到您真正需要使用它们为止。

于 2013-04-20T01:04:30.623 回答
2

GHC 很有可能内联handleHelpand handleOpt1,从而避免调用开销 -查看生成的 Core(编译器的中间表示)以确定确定。

如果由于某种原因,这些函数没有被内联,你可以用pragma标记它们INLINE

handleHelp :: IO ()
handleHelp = print "help"
{-# INLINE handleHelp #-}

handleOpt1 :: Option -> IO ()
handleOpt1 (Opt1 n f s) = print (n, f, s)
{-# INLINE handleOpt1 #-}

您还可以依靠内联来避免解构以下参数handleOption

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

undefined只是为了消除有关非详尽模式匹配的警告。或者,您可以删除此行并-fno-warn-incomplete-patterns为此模块启用。

查看生成的 Core 我们可以看到 的undefined分支handleOpt1被淘汰了:

handleOpt2
  :: Option
     -> State# RealWorld
     -> (# State# RealWorld, () #)
handleOpt2 =
  \ (ds_dl7 :: Option)
    (eta_Xh :: State# RealWorld) ->
    case ds_dl7 of _ {
      Help -> ...  
      Opt1 n_aaq f_aar s_aas -> ...

main1
  :: State# RealWorld
     -> (# State# RealWorld, () #)
main1 =
  \ (eta_Xk :: State# RealWorld) ->
    handleOpt2 (Opt1 2 3.0 "") eta_Xk

不过,我更喜欢原始版本,因为它排除了handleOpt1.

于 2013-04-19T22:21:53.257 回答
0

我喜欢 Ben 的回答,但或者,您可以引入更多类型。

数据 Opt1Params = Opt1Params Int Double String

数据选项 = 帮助 | 选项 1 选项 1 参数

handleOption Help = handleHelp handleOption (Opt1 params) = handleOpt1 params

handleOpt1 (Opt1Params nfs) = ...

于 2015-11-19T12:47:18.187 回答