SO是一场狗屎秀。感谢您的搭车。
您可以使用 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)
)通常不适用于它们。您应该仔细考虑在您的情况下使用它们的利弊。
在这个特定的例子中,通过抛弃
handleHelp
和 handleOpt1
并使它们成为 handleOption
函数的独立方程来解决“问题”似乎更自然:
handleOption :: Option -> IO ()
handleOption Help = print "help"
handleOption (Opt1 n f s) = print (n, f, s)
这让您两全其美。您可以为每种情况编写一个单独的方程(因此,即使每个案例很大,您也可以防止它们合并成一个巨大的方程),您不必编写任何样板“调度”函数,也不必说出
Opt1
外壳的各个部分的名称,直到您真正需要使用它们为止。
GHC 内联
handleHelp
和 handleOpt1
的可能性很大,从而避免了调用开销 - 查看生成的 Core(编译器的中间表示)以确定答案。
如果由于某种原因,这些函数没有被内联,您可以用 INLINE
pragma: 标记它们
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
中模式匹配失败的可能性。
我喜欢 Ben 的回答,但或者,你也可以引入更多类型。
数据 Opt1Params = Opt1Params Int 双字符串
数据选项 = 帮助 | Opt1 Opt1Params
handleOption帮助=handleHelp handleOption(Opt1 参数)=handleOpt1 参数
handleOpt1 (Opt1Params n f s) = ...