F# 正则表达式匹配链

3

由于我对 F# 的正则表达式实现并不完全满意,所以我想要实现一个所谓的“正则表达式链”。它的基本工作方式如下:

给定字符串 s 将被检查是否匹配第一个模式。如果匹配,则应执行与第一个模式关联的函数。如果没有匹配,则应继续进行下一个。

我尝试这样实现:

let RegexMatch ((s : string, c : bool), p : string, f : GroupCollection -> unit) =
    if c then
        let m = Regex.Match(s, p)
        if m.Success then
            f m.Groups
            (s, false)
        else (s, c)
    else (s, c)


("my input text", true)
|> RegexMatch("pattern1", fun g -> ...)
|> RegexMatch("pattern2", fun g -> ...)
|> RegexMatch("pattern3", fun g -> ...)
|> .... // more patterns
|> ignore

问题在于,这段代码无效,因为前向管道操作符似乎不能传递元组,或者不喜欢我的实现“设计”。

我的问题是:我能否轻松修复上面的代码,还是应该实现其他类型的正则表达式链?


2
由于我对F#的正则表达式实现不完全满意,需要指出F#没有正则表达式实现。.NET有,.NET Core有,Mono也有,但是F#并不知道正则表达式是什么。 - ildjarn
另一种方法是将数据导向:将正则表达式函数对放入列表中,并使用 List.tryPick 运行第一个匹配的正则表达式的函数。这将允许您动态地构建正则表达式函数的情况。 - TheQuickBrownFox
2个回答

6
你的函数RegexMatch不支持管道,因为它有元组参数。
首先,看一下管道的定义:
let (|>) x f = f x

从这里可以清楚地看到,这个表达式:
("text", true)
|> RegexMatch("pattern", fun x -> ...)

将等同于此:

RegexMatch("pattern", fun x -> ...) ("text", true)

这符合你的函数签名吗?显然不是。在你的签名中,文本/布尔对首先出现,并与模式和函数一起构成参数三元组。

要使其工作,您需要以柯里化形式将“piped”参数放在最后:

let RegexMatch p f (s, c) = ...

然后你可以进行管道操作:
("input", true)
|> RegexMatch "pattern1" (fun x -> ...)
|> RegexMatch "pattern2" (fun x -> ...)
|> RegexMatch "pattern3" (fun x -> ...)

作为旁注,我必须指出你的方法并不是非常,呃,函数式。你的整个逻辑基于副作用,这将使你的程序不可组合、难以测试,并且可能容易出现错误。你没有充分利用F#的优势,实际上只是在使用“带有更好语法的C#”。此外,实际上有研究良好的方法可以实现你想要的功能。首先,可以查看铁路导向编程(也称为单子计算)。

2
对我来说,这听起来像是你正在尝试实现Active Patterns

使用活动模式,您可以使用常规的模式匹配语法来匹配正则表达式模式:

let (|RegEx|_|) p i =
  let m = System.Text.RegularExpressions.Regex.Match (i, p)
  if m.Success then
    Some m.Groups
  else
    None

[<EntryPoint>]
let main argv = 
  let text = "123"
  match text with
  | RegEx @"\d+" g -> printfn "Digit: %A" g
  | RegEx @"\w+" g -> printfn "Word : %A" g
  | _              -> printfn "Not recognized"
  0

另一种方法是使用Fyodor所称的铁路导向编程:
type RegexResult<'T> = 
  | Found     of  'T
  | Searching of  string

let lift p f = function
  | Found v     -> Found v
  | Searching i -> 
    let m = System.Text.RegularExpressions.Regex.Match (i, p)
    if m.Success then
      m.Groups |> f |> Found
    else
      Searching i

[<EntryPoint>]
let main argv = 
  Searching "123"
  |> lift @"\d+" (fun g -> printfn "Digit: %A" g)
  |> lift @"\w+" (fun g -> printfn "Word : %A" g)
  |> ignore
  0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接