我有一个这样的数据类型和函数:
data Expr = Num Int | Add Expr Expr | Mult Expr Expr | Neg Expr | If Expr Expr Expr deriving (Show, Read)
prettyPrint :: Expr -> IO ()
prettyPrint expr = prettyPrint' expr 0
prettyPrint' :: Expr -> Int -> IO ()
prettyPrint' (Num x) i = putStrLn $ concat (replicate i " ") ++ "Num " ++ show x
prettyPrint' (Add x y) i = do
putStrLn $ concat (replicate i " ") ++ "Add"
prettyPrint' x (i+1)
prettyPrint' y (i+1)
prettyPrint' (Mult x y) i = do
putStrLn $ concat (replicate i " ") ++ "Mult"
prettyPrint' x (i+1)
prettyPrint' y (i+1)
prettyPrint' (Neg x) i = do
putStrLn $ concat (replicate i " ") ++ "Neg"
prettyPrint' x (i+1)
prettyPrint' (If x y z) i = do
putStrLn $ concat (replicate i " ") ++ "If"
prettyPrint' x (i+1)
prettyPrint' y (i+1)
prettyPrint' z (i+1)
Run Code Online (Sandbox Code Playgroud)
在我正在使用模式匹配的函数中.问题是他们的代码重用很多.例如,情况Mult和Add基本相同的代码.这同样适用于Num和Neg.有没有办法根据表达式有多少变量来编写?像一个for Num和Neg,因为他们只有一个变量.在一种情况下Mult和Add,因为他们有两个变量.最后一个案例If,因为该表达式有三个变量.
注意:
我找到了这个答案,我认为这是一个比我开始时更好的解决方案:
prettyPrint :: Expr -> IO ()
prettyPrint expr = putStrLn (prettyPrint' 1 expr)
prettyPrint' :: Int -> Expr -> String
prettyPrint' i (Num x) = "Num " ++ show x
prettyPrint' i expr =
let indent x = concat (replicate i " ") ++ x
(op, args) = case expr of
Add x y -> ("Add", [x,y])
Mult x y -> ("Mult", [x,y])
Neg x -> ("Neg", [x])
If x y z -> ("If", [x,y,z])
in intercalate "\n" (op : map (indent . prettyPrint' (i + 1)) args)
Run Code Online (Sandbox Code Playgroud)
首先,我会尽可能长时间地远离 IO monad。返回prettyPrint'一个要打印的字符串。
prettyPrint :: Expr -> IO ()
prettyPrint = putStrLn . prettyPrint'
Run Code Online (Sandbox Code Playgroud)
现在,唯一的工作prettyPrint'是创建要打印的(可能是多行)字符串。对于数字来说,这很简单:只需使用show实例即可。
prettyPrint' :: Expr -> String
prettyPrint' e@(Num _) = show e
-- or, ignoring the Show instance for Expr altogether
-- prettyPrint' (Num x) = "Num " ++ show x
Run Code Online (Sandbox Code Playgroud)
对于其余的,有一个模式:
那看起来像
prettyPrint' expr = let indent x = " " ++ x
(op, args) = case expr of
Add x y -> ("Add", [x,y])
Mult x y -> ("Mult", [x,y])
Neg x -> ("Neg", [x])
If x y z -> ("If", [x,y,z])
in intercalate "\n" (op : map (indent . prettyPrint') args)
Run Code Online (Sandbox Code Playgroud)
举个例子,考虑一下prettyPrint'表达式 的作用Add (Num 3) (Num 5)。首先,它设置op为"Add"和args到[Num 3, Num 5]。接下来,它映射indent . prettyPrint'参数列表,以获得[" Num 3", " Num 5"]。将运算符放在列表的前面yields ["Add", " Num 3", " Num 3"],然后将它们与intercalateproducts连接起来"Add\n Num 3\n Num 5"。
唯一剩下的样板位于case表达式中。我认为可以消除这种情况,但它需要我不熟悉的通用编程水平。我确信其他人可能会用我的答案来解决这个问题。