unknown6656
unknown6656

Reputation: 2963

F# Regex matching chain

As I am not completely happy with F#'s regex implementation for my usage, I wanted to implement a so-called regex chain. It basically works as follows:

The given string s will be checked, whether it matches the first pattern. If it does, it should execute a function associated with the first pattern. If it does not, it should continue with the next one.

I tried to implement it as follows:

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

The problem is, that this code is invalid, as the forward-pipe operator does not seem to pipe tuples or does not like my implementation 'design'.

My question is: Can I fix this code above easily or should I rather implement some other kind of regex chain?

Upvotes: 3

Views: 860

Answers (2)

To me this sounds like what you are trying to implement is Active Patterns.

Using Active Patterns you can use regular pattern matching syntax to match against RegEx 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

Another approach is to use what Fyodor refers to as Railway Oriented Programming:

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

Upvotes: 2

Fyodor Soikin
Fyodor Soikin

Reputation: 80805

Your function RegexMatch won't support piping, because it has tupled parameters.

First, look at the definition of the pipe:

let (|>) x f = f x

From this, one can clearly see that this expression:

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

would be equivalent to this:

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

Does this match your function signature? Obviously not. In your signature, the text/bool pair comes first, and is part of the triple of parameters, together with pattern and function.

To make it work, you need to take the "piped" parameter in curried form and last:

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

Then you can do the piping:

("input", true)
|> RegexMatch "pattern1" (fun x -> ...)
|> RegexMatch "pattern2" (fun x -> ...)
|> RegexMatch "pattern3" (fun x -> ...)

As an aside, I must note that your approach is not very, ahem, functional. You're basing your whole logic on side effects, which will make your program not composable and hard to test, and probably prone to bugs. You're not reaping the benefits of F#, effectively using it as "C# with nicer syntax".

Also, there are actually well researched ways to achieve what you want. For one, check out Railway-oriented programming (also known as monadic computations).

Upvotes: 6

Related Questions