Parsing 解析:F中的惰性初始化和相互递归的monad#

Parsing 解析:F中的惰性初始化和相互递归的monad#,parsing,f#,recursion,lazy-evaluation,Parsing,F#,Recursion,Lazy Evaluation,我一直在用F#(有点类似于)编写一个小的一元解析器组合器库,现在尝试为编程语言实现一个小的解析器 我首先用Haskell(使用Parsec)实现了这段代码,它运行得非常好。 中缀表达式的解析器是相互递归设计的 parseInfixOp :: Parser String -> Parser Expression -> Parser Expression parseInfixOp operatorParser subParser = ignoreSpaces $ do

我一直在用F#(有点类似于)编写一个小的一元解析器组合器库,现在尝试为编程语言实现一个小的解析器

我首先用Haskell(使用Parsec)实现了这段代码,它运行得非常好。 中缀表达式的解析器是相互递归设计的

parseInfixOp :: Parser String -> Parser Expression -> Parser Expression
parseInfixOp operatorParser subParser = ignoreSpaces $ do
                                          x <- ignoreSpaces $ subParser
                                          do
                                            op <- ignoreSpaces $ operatorParser
                                            y  <- parseInfixOp operatorParser subParser
                                            return $ BinaryOp op x y
                                           <|> return x

parseInfix :: [String] -> Parser Expression -> Parser Expression
parseInfix list = parseInfixOp (foldl1 (<|>) $ map string list)

parseExpr :: Parser Expression
parseExpr = parseInfix0

parseInfix0 = parseInfix ["==", "<>", "And", "Or", "Xor", "<", ">", "<=", ">="] parseInfix1
parseInfix1 = parseInfix ["+", "-", "Mod"] parseInfix2
parseInfix2 = parseInfix ["*", "/", "\\"] parseInfix3
parseInfix3 = parseInfix ["^"] parseInfix4
parseInfix4 = parseFactor

parseFactor :: Parser Expression
parseFactor = parseFactor' <|> (betweenChars '(' ')' parseExpr)

parseFactor' :: Parser Expression
parseFactor' = parseString
           <|> parseBool
           <|> parseNumber
           <|> parseVariable
           <|> (try parseFunCall) <|> parseIdentifier  
parseInfixOp::解析器字符串->解析器表达式->解析器表达式
parseInfixOp运算符parser subParser=ignoreSpaces$do

x递归对象的警告是什么?显示文本;在这种情况下,有一个这样的警告是不可忽视的(事实上,在某种意义上是可取的)

如果由于递归值而无法编译,只需将“语法值”转换为“语法函数”。也就是说,而不是

...
and parseInfix2 = body
...
使用

即使“parseInfix2”的类型在任何情况下都是相同的函数类型。。。F#(与Haskell不同)有时会要求您明确(如上所述)

我会忽略关于插入“lazy”的建议,解析器实际上是函数,而不是值,因此eta转换将涉及相同的问题(所有这些都不会被急切地评估,在任何东西开始“运行”之前,都需要“等待”直到传递要解析的字符串)


关于StackOverflowExceptions,如果您发布堆栈的循环片段,可能会有所帮助,但您可以自己查看,例如,如果语法的左递归部分不使用任何输入,并且被困在循环中。我认为对于大多数语言中的大多数解析技术来说,这是一个很容易犯的错误。

η-转换不一定是一个很好的解决方案-如果你这样做,你必须证明延迟函数最多运行一次,或者在解析过程中调用它会付出很多开销

你想要这样的东西:

let rec p1 = lazy (...)
and p2 = ... p1.Value ..
如果您有工作流生成器,更好的方法是定义延迟成员来为您执行此操作:

type Builder() =
    member this.Delay(f: unit -> Parser<_>) : Parser<_> = 
        let promise = Lazy.Create f
        Return () |> Bind (fun () -> promise.Value)

let parse = new Builder()


let rec p1 =
    parse {
        ...
    }

and p2 =
    parse {
        ...
    }
类型生成器()=
成员this.Delay(f:unit->Parser):Parser=
让承诺=懒惰。创建f
Return()|>Bind(fun()->promise.Value)
让parse=newbuilder()
让我们记录p1=
解析{
...
}
和p2=
解析{
...
}

eta重写和延迟都不是必然的事。F#编译器似乎很难处理深层递归。对我来说,有效的方法是将递归折叠成单个顶级函数(通过将要递归调用的函数作为参数传递)。这个顶层是eta风格的

顶级,我有:

let rec myParser s = (parseExpr myParser) s

注意维基百科说:“在像OCaml这样严格的语言中,我们可以通过强制使用闭包来避免无限递归问题。”。这就是我的工作原理。

完美-显式η-转换是我的解决方案!谢谢你。我最近提出了一个类似的方法。
type Builder() =
    member this.Delay(f: unit -> Parser<_>) : Parser<_> = 
        let promise = Lazy.Create f
        Return () |> Bind (fun () -> promise.Value)

let parse = new Builder()


let rec p1 =
    parse {
        ...
    }

and p2 =
    parse {
        ...
    }
let rec myParser s = (parseExpr myParser) s