Haskell - 与数据类型匹配的模式

Chr*_*ian 6 haskell

我有一个这样的数据类型和函数:

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)

che*_*ner 2

首先,我会尽可能长时间地远离 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)

对于其余的,有一个模式:

  1. 识别构造函数
  2. 确定其论点
  3. 用换行符连接构造函数名称及其漂亮打印的参数。每个参数将相对于其运算符缩进一级;递归将处理多层缩进。

那看起来像

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表达式中。我认为可以消除这种情况,但它需要我不熟悉的通用编程水平。我确信其他人可能会用我的答案来解决这个问题。