Scala 3:为什么`inline`修复堆栈溢出

k97glaaz  于 2023-10-18  发布在  Scala
关注(0)|答案(2)|浏览(108)

我有一个导致堆栈溢出的代码。

// Lexer.Token defined elsewhere
object BadParser extends Parsers {
  import Lexer.Token
  type Elem = Token
  private def PUNCT = Token.PUNCT

 

  private def parens[T](p: Parser[T]) = PUNCT("(") ~ p ~ PUNCT(")")

 

  def expr: Parser[Any] = parens(expr) | PUNCT(".")
}
class ParensTest extends AnyFlatSpec {
  def testBad(input: String) =
    val tokens = Lexer.scan(input)
    BadParser.expr(tokens)
  "bad parser" should "not recur forever but it does" in {
    testBad("((.))")
  }
}

这已经够奇怪的了,但还有更奇怪的当我试着像这样检查括号(expr)时:

def expr: Parser[Any] = log(parens(expr))("parens expr") | PUNCT(".")

不再有堆栈溢出。
我决定尝试像这样内联parans函数和参数:

inline parens(inline p... and that also fixed it.

我的困惑是,内联https://docs.scala-lang.org/scala3/guides/macros/inline.html的文档说内联参数不应该改变函数的语义,但它显然改变了。我错过了什么?
(my对内联的理解是,它迫使编译器实际内联,而不仅仅是给予最大的努力,当你说参数内联时,它将内联参数,而不是通过值或引用传递)
编辑:我使用Scala Parser Combinator库

icomxhvb

icomxhvb1#

By-name(也称为lazy evaluation)可以防止无限递归。即

private def parens[T](p: => Parser[T]) = PUNCT("(") ~ p ~ PUNCT(")")

操作符~的参数被定义为by-name,因此内联会对您有所帮助。然而,如果内联了太多的调用站点,则不会给予好的结果。

sirbozc5

sirbozc52#

你在写的时候有一个无限递归:

def expr = parens(expr) | PUNCT(".")

expr在能够调用parens之前调用了自己。
为什么内联时它会消失?
内联代码就像你写了下面的代码:

def expr = (PUNCT("(") ~ expr ~ PUNCT(")")) | PUNCT(".")

现在,* 这是一个假设,因为我们没有完整的代码,* expr首先调用~方法,我猜在你的情况下不需要计算expr

相关问题