You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用FParsec解析时如何将单个项映射至OneThing而非LotsOfThings

问题:如何让FParsec解析返回正确的Thing可区分联合类型?

我最近在用FParsec处理解析需求,想要把单个项或者项列表解析后存入一个叫Thing的可区分联合(DU)里。目前的实现代码如下,但遇到个问题——哪怕只有单个项的时候,返回的依然是LotsOfThings类型:

open FParsec

let test p str = 
    match run p str with 
    | Success(result, _, _) -> printfn "Success: %A" result 
    | Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg

type Thing = 
    | OneThing of int 
    | LotsOfThings of Thing list

let str s = pstringCI s .>> spaces

let one = str "one" |>> fun x -> OneThing 1
let two = str "two" |>> fun x -> OneThing 2
let three = str "three" |>> fun x -> OneThing 3

let oneThing = (one <|> two <|> three)
let lotsOfThings = sepBy1 oneThing (str "or") |>> LotsOfThings
let lotsFirst = (lotsOfThings <|> oneThing)

test lotsFirst "one or two" // Success: LotsOfThings [OneThing 1; OneThing 2]
test lotsFirst "one" // Success: LotsOfThings [OneThing 1]

我试过用解析后检查列表长度的方式来处理,但总觉得这种做法不够优雅:

let lotsOfThings = sepBy1 oneThing (str "or") |>> fun l -> 
    if l.Length = 1 then l.[0] else l |> LotsOfThings

正确的实现方式

其实我们可以直接从解析器层面区分单个项和多项的情况,不需要先解析成列表再做判断。核心思路是把解析逻辑拆分为“单个项”加上“零个或多个or+项”的组合,这样就能在解析阶段直接生成对应的Thing类型:

open FParsec

type Thing = 
    | OneThing of int 
    | LotsOfThings of Thing list

let str s = pstringCI s .>> spaces

let one = str "one" |>> fun _ -> OneThing 1
let two = str "two" |>> fun _ -> OneThing 2
let three = str "three" |>> fun _ -> OneThing 3

let oneThing = one <|> two <|> three

// 解析"or"后面跟着单个项的结构
let orThing = str "or" >>. oneThing

// 组合解析器:先解析第一个项,再解析后续所有的or+项
let thingParser = 
    oneThing .>>. many orThing 
    |>> fun (firstItem, restItems) ->
        match restItems with
        | [] -> firstItem // 没有后续项,返回单个OneThing
        | _ -> LotsOfThings (firstItem :: restItems) // 有后续项,组合成LotsOfThings

let test p str = 
    match run p str with 
    | Success(result, _, _) -> printfn "Success: %A" result 
    | Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg

// 测试案例
test thingParser "one or two" // Success: LotsOfThings [OneThing 1; OneThing 2]
test thingParser "one" // Success: OneThing 1
test thingParser "two or three or one" // Success: LotsOfThings [OneThing 2; OneThing 3; OneThing 1]

思路说明

这里没有使用sepBy1(它会强制把单个项也包装成单元素列表),而是拆分了解析逻辑:

  1. 先解析第一个独立的项;
  2. 再解析零个或多个以or开头的后续项;
  3. 最后根据后续项是否存在,直接生成对应的Thing类型——没有后续项就返回OneThing,有后续项就把所有项组合成列表返回LotsOfThings。

这种方式更贴合解析器的设计思想,避免了事后判断列表长度的“补丁式”处理,代码逻辑也更清晰。

内容的提问来源于stack exchange,提问作者Sean Kearon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:03:39