【问题标题】:Is there something like cata but where you can match inner structure?有没有像猫一样的东西,但你可以匹配内部结构?
【发布时间】:2016-12-30 13:50:14
【问题描述】:

我有这种语言 AST

data ExprF r = Const Int
              | Var   String
              | Lambda String r
              | EList [r]
              | Apply r r
 deriving ( Show, Eq, Ord, Functor, Foldable )

我想把它转换成字符串

toString = cata $ \case
  Const x -> show x
  Var x -> x
  EList x -> unwords x
  Lambda x y -> unwords [x, "=>", y]
  Apply x y -> unwords [x, "(", y, ")"]

但是当 Apply 中使用 lambda 时,我需要括号

(x => x)(1)

但我无法将内部结构与 cata 匹配

toString :: Fix ExprF -> String
toString = cata $ \case
  Const x -> show x
  Var x -> x
  Lambda x y -> unwords [x, "=>", y]
  Apply (Lambda{}) y -> unwords ["(", x, ")", "(", y, ")"]
  Apply x y -> unwords [x, "(", y, ")"]

还有比para更好的解决方案吗?

toString2 :: Fix ExprF -> String
toString2 = para $ \case
  Const x -> show x
  Var x -> x
  Lambda x (_,y) -> unwords [x, "=>", y]
  EList x -> unwords (snd <$> x)
  Apply ((Fix Lambda {}),x) (_,y) -> unwords ["(", x, ")", "(", y, ")"]
  Apply (_,x) (_,y) -> unwords [x, "(", y, ")"]

看起来更丑。即使只在一个地方需要它,我也需要在所有地方删除 fst 元组参数,我想它会更慢。

【问题讨论】:

  • 在一般情况下,您可以采用 showsPrec 中的优先级参数。不过,帕拉对我来说并不算太糟糕。
  • 您能详细说明一下吗?我看不出我可以把这个论点放在哪里。在这种情况下,para 是可以的,但是当 ast 更大时,噪音太大了
  • 您可以使用 cata 构建一个函数:类似cata $ \case Var x -&gt; const x ; Apply x y -&gt; \_ -&gt; unwords [x 5, y 5] ; Lambda x y -&gt; (\p -&gt; if p==5 then addParen (x 0) (y 0) else noParen (x 0) (y 0) ; ... 根据需要调整优先级数,并在顶层传递一个初始 0,以便结果是一个字符串。
  • @ais 您可能喜欢this question on pretty-printing boolean formulae 中的讨论,了解更多关于showsPrec 方法的信息。
  • @ais Bool 如果您只有两个优先级,则很好。在一般情况下,您需要更多。上面 Daniel Wagner 的链接就是这样一个例子。

标签: haskell recursion abstract-syntax-tree catamorphism recursion-schemes


【解决方案1】:

正如@chi、@DanielWagner 和我在 cmets 中指出的那样,以结构递归方式进行这种带括号的漂亮打印的方法是“showsPrec 方法”。

最大的想法不是将语法树折叠成String,而是折叠成一个函数 Bool -&gt; String。这使我们在折叠中具有一定程度的上下文敏感性:我们将使用额外的Bool 参数来跟踪我们当前是否处于应用程序左侧的上下文中。

parens x = "(" ++ x ++ ")"

ppAlg :: ExprF (Bool -> String) -> (Bool -> String)
ppAlg (Const x) isBeingApplied = show x
ppAlg (Var x) isBeingApplied = x
ppAlg (Lambda name body) isBeingApplied = p ("\\" ++ name ++ " -> " ++ body False)
    where p = if isBeingApplied then parens else id
ppAlg (EList es) isBeingApplied = unwords (sequenceA es False)
ppAlg (Apply fun arg) isBeingApplied = fun True ++ " " ++ arg False

我们将isBeingApplied 的值传递给递归调用,具体取决于我们现在在语法树中的位置。请注意,我们传递True 的唯一地方是作为fun 的参数在Apply 案例的主体中。然后,在Lambda 的情况下,我们检查该论点。如果当前项是应用程序的左侧部分,我们将括号括起来;如果不是,我们不这样做。

在顶层,将整个树折叠成一个函数Bool -&gt; String,我们将参数False 传递给它——我们目前不在应用程序的上下文中——得到一个String .

pp :: Expr -> String
pp ex = cata ppAlg ex False

ghci> pp $ app (lam "x" (var "x")) (cnst 2)
"(\\x -> x) 2"

通过将Bool 替换为Int,此方法可以推广到具有任意优先级的括号运算符,如@DanielWagner's linked answer 所述。

【讨论】:

  • 一个函数Bool -&gt; String只是一对(String, String)。所以你把它折叠两次,带括号和不带括号,然后在递归期间选择你想要的那个。太棒了,+1
  • @V.Semeria 是的!这是完全正确的。 Bool -&gt; String 版本更好。你做的工作更少(因为你只实际上折叠一次)。它还可以更好地扩展到具有两个以上优先级的语法:例如,如果您的语言中有 5 个优先级,那么使用 Ints(或具有五个值的某种类型,如果您想全部使用)比工作容易得多带有元组 (String, String, String, String, String).
【解决方案2】:

一种解决方案是使用{-# LANGUAGE PatternSynonyms #-} 扩展并定义单向模式,例如:

pattern Apply' r1 r2 <- Apply (_,r1) (_,r2)

然后你可以像这样在你的定义中使用:

toString2 :: Fix ExprF -> String
toString2 = para $ \case
  Const x -> show x
  Var x -> x
  Lambda x (_,y) -> unwords [x, "=>", y]
  EList x -> unwords (snd <$> x)
  Apply ((Fix Lambda {}),x) (_,y) -> unwords ["(", x, ")", "(", y, ")"]
  Apply' x y -> unwords [x, "(", y, ")"]

由于ExprF 是一个函子,另一种选择是简单地写:

toString2' :: Fix ExprF -> String
toString2' = para $ \case
  Apply ((Fix Lambda {}),x) (_,y) -> unwords ["(", x, ")", "(", y, ")"]
  other -> case fmap snd other of
      Const x -> show x
      Var x -> x
      Lambda x y -> unwords [x, "=>", y]
      Apply x y -> unwords [x, "(", y, ")"]

使用模式同义词并使用-Wall 进行编译时,我无法让穷举检查器相信模式匹配是穷举的。

【讨论】:

  • 这使代码更短,但它并没有直接解决问题。 OP 想使用结构递归来编写漂亮的打印机
  • 详尽检查器在模式同义词(或至少不平凡的同义词)方面完全被破坏。还没有人能解决。
【解决方案3】:

对于丢失的情况如何直接递归:

toString :: Fix ExprF -> String
toString (Fix (Apply (Fix (Lambda _ x)) y)) = "(" ++ toString x ++ ")(" ++ toString y ++ ")"
toString z = (cata $ \case
  Const x -> show x
  Var x -> x
  EList x -> unwords x
  Lambda x y -> unwords [x, "=>", y]
  Apply x y -> unwords [x, "(", y, ")"]) z

【讨论】:

  • 这将失败,因为 cata 不使用第一种情况进行递归,例如在Apply (Apply (Lambda ..) ..) ..
  • 然后对每个 ExprF 构造函数进行递归。它不会比 para 长多少,而且可能会更快。
  • 我认为问题的重点在于避免编写显式递归。
  • @DanielWagner 没有必要。重点是找到解决问题的更好方法。
猜你喜欢
  • 2010-12-23
  • 1970-01-01
  • 2013-07-30
  • 2014-09-29
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多