Skip to content
AgileLogics
6 min read

Strings and Regular Expressions

Prerequisites: Strings

More string tools

open System

let csvLine = "  Ada Lovelace, 36 ,London  "

let fields = csvLine.Split(',') |> Array.map _.Trim()
printfn "%A" fields

printfn "%s" (String.Join(" | ", fields))
printfn "%s" ("hello".PadLeft(8, '.'))
printfn "%s" ("F#".PadRight(6) + "|")
printfn "%b" (String.IsNullOrWhiteSpace "   ")
printfn "%d" ("banana".IndexOf "an")
printfn "%s" (String("ab".ToCharArray() |> Array.rev))
printfn "%A" ("a,b,,c".Split(',', StringSplitOptions.RemoveEmptyEntries))
[|"Ada Lovelace"; "36"; "London"|]
Ada Lovelace | 36 | London
...hello
F#    |
true
1
ba
[|"a"; "b"; "c"|]

Strings are sequences of characters, so the Seq functions work on them too:

let text = "Hello, World 2025!"

printfn "%d" (text |> Seq.filter System.Char.IsUpper |> Seq.length)
printfn "%s" (text |> String.filter System.Char.IsLetter)
printfn "%s" (text |> String.map System.Char.ToLower)
printfn "%b" (text |> String.exists System.Char.IsDigit)
2
HelloWorld
hello, world 2025!
true

Building strings efficiently

Concatenating in a loop creates many temporary strings. For large output, use String.concat / String.Join, or a StringBuilder:

let sb = System.Text.StringBuilder()
for i in 1 .. 3 do
    sb.Append($"line {i}; ") |> ignore
printfn "%s" (sb.ToString())
line 1; line 2; line 3;

|> ignore throws away a result you don't need — here, the builder that Append returns. F# warns if you silently discard a non-unit value.

Regular expressions

.NET's System.Text.RegularExpressions.Regex works directly from F#:

open System.Text.RegularExpressions

let log = "2025-03-09 ERROR disk full; 2025-03-10 WARN cpu high; 2025-03-11 ERROR timeout"

printfn "%b" (Regex.IsMatch(log, @"ERROR"))

for m in Regex.Matches(log, @"(\d{4}-\d{2}-\d{2}) (\w+) ([^;]+)") do
    printfn "%s | %-5s | %s" m.Groups[1].Value m.Groups[2].Value m.Groups[3].Value

printfn "%s" (Regex.Replace("call 555-0100 or 555-0199", @"\d{3}-\d{4}", "XXX-XXXX"))
printfn "%A" (Regex.Split("one, two;three", @"[,;]\s*"))
true
2025-03-09 | ERROR | disk full
2025-03-10 | WARN  | cpu high
2025-03-11 | ERROR | timeout
call XXX-XXXX or XXX-XXXX
[|"one"; "two"; "three"|]

Use verbatim strings (@"...") or triple-quoted strings for patterns so backslashes stay literal. You can try patterns interactively with the Regex Tester.

Named groups

open System.Text.RegularExpressions

let pattern = Regex(@"^(?<user>[\w.]+)@(?<domain>[\w.]+)$")

for email in ["ada@example.com"; "not-an-email"] do
    let m = pattern.Match email
    if m.Success then
        printfn "user=%s domain=%s" m.Groups["user"].Value m.Groups["domain"].Value
    else
        printfn "%s: no match" email
user=ada domain=example.com
not-an-email: no match

Creating a Regex object once and reusing it is faster than calling the static methods repeatedly with the same pattern. Later in this level, active patterns turn regexes into clean match cases.

Practice

Extract every hashtag (a # followed by letters or digits) from "Loving #fsharp and #dotnet10 today #fp" and print them as a list, without the #.

Show solution
open System.Text.RegularExpressions

Regex.Matches("Loving #fsharp and #dotnet10 today #fp", @"#(\w+)")
|> Seq.map (fun m -> m.Groups[1].Value)
|> List.ofSeq
|> printfn "%A"
["fsharp"; "dotnet10"; "fp"]