Kai Ito
Recursively replacing parts of a URL

Recursively replacing parts of a URL

I’m slowly building a .Net Core global tool to do some REST API testing. One idea I had for this project was to be able to generate multiple URLs with different variables. e.g. http://www.example.com/?id={{INT}} would generate several URLs with the {{INT}} part replaced with randomly generated integers. The solution that I came up with for this, was to use regular expressions. {{(.*?)}} is the regex I came up with to find all parts enclosed by double brackets.

Now came the task of actually replacing the brackets. As I have a strong C# background, an approach I like to use is to code what I want to accomplish in an OOP or imperative style, and then to refactor it to a functional style.

Here my imperative approach:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let mutable regexMatch = variableRegex.Match("http://www.example.com/?id={{INT}}")
let mutable currentIndex = 0
while regexMatch.Success do
    let variableToReplace = regexMatch.Groups.[0]
    let length = variableToReplace.Index - currentIndex
    let subUrl = url.Substring(currentIndex, length)
    builder.Append(subUrl) |> ignore
    let replaceWith =
        match regexMatch.Groups.[1].Value with
        | "INT" -> "INTEGER" // replace with int generator
        | _ -> "OBJECT" // optionally other data types
    builder.Append(replaceWith) |> ignore
    currentIndex <- subUrl.Length + variableToReplace.Length
    regexMatch <- regexMatch.NextMatch()
builder.ToString()

And here after refactoring to a functional recursive Approach:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let rec build currentIndex (currentMatch: Match) =
    if currentMatch.Success then
        let variableToReplace = currentMatch.Groups.[0]
        let length = variableToReplace.Index - currentIndex
        let subUrl = url.Substring(currentIndex, length)
        builder.Append(subUrl) |> ignore
        let replaceWith =
            match currentMatch.Groups.[1].Value with
            | "INT" -> "INTEGER" // replace with int generator
            | _ -> "OBJECT" // optionally other data types
        builder.Append(replaceWith) |> ignore
        build (subUrl.Length + variableToReplace.Length) (currentMatch.NextMatch())
    else
        builder.ToString()
build 0 (variableRegex.Match(url))

This approach has really helped me in not ripping my hair out while I’m trying to simultaneously figure out what it is I need to do, while also figuring out how to properly recurse over whatever it is I’m doing.

Something specific to take note of, is that the imperative approach has two mutable variables. Coincidentally, the recursive version has two values passed in as parameters. Or maybe this isn’t a coincidence, and is a very good starting point as to how to go about the refactor.

The keen reader would also point out that the recursive version isn’t actually functional. There are side effects. It is appending a string to a StringBuilder. This is a place where I opted for (premature) optimization, as appending strings is expensive. The purely functional approach would simply also pass in the string to build as a parameter.

val mutable regexMatch : 'a
val variableRegex : Regex
val mutable currentIndex : 'a
val build : currentIndex:'a -> currentMatch:Match -> 'b
val currentIndex : 'a
val currentMatch : Match
type Match =
  inherit Group
  member Groups : GroupCollection
  member NextMatch : unit -> Match
  member Result : replacement:string -> string
  static member Empty : Match
  static member Synchronized : inner:Match -> Match