← Code Compare

Regex & Text

The shared task - pull every key=value pair out of "alice=30, bob=25" with a capturing regex and print alice is 30; bob is 25 - exposes a real divide on the BEAM. Erlang, Elixir, Gleam, and LFE all reach for the same regex engine: OTP's re module, which wraps PCRE (Perl-compatible). The key idiom is global scan with captures: ask for all matches and grab the parenthesised groups, then format the survivors. Luerl is the outlier - it runs Lua, whose string library has no PCRE at all but its own lighter-weight Lua patterns (%a, %d, no alternation or backtracking), so watch the syntax shift from (\w+)=(\d+) to (%a+)=(%d+).

Show: ErlangElixirGleamLFELuerl
Erlang
-module(regex_text).
-export([run/0]).

run() ->
    Input = "alice=30, bob=25",
    %% [global] returns every match; all_but_first keeps only the
    %% capture groups, so each match is [Key, Value] as char lists.
    {match, Pairs} = re:run(Input, "(\\w+)=(\\d+)",
                            [global, {capture, all_but_first, list}]),
    Parts = [io_lib:format("~s is ~s", [K, V]) || [K, V] <- Pairs],
    io:format("~s~n", [lists:join("; ", Parts)]).
%% => alice is 30; bob is 25

Erlang's re module is a PCRE wrapper; re:run/3 with [global, {capture, all_but_first, list}] returns one [Key, Value] per match, which a list comprehension formats and lists:join/2 stitches with ; .

Elixir
input = "alice=30, bob=25"

# ~r/.../ compiles a PCRE regex; Regex.scan returns one list per
# match: [full, capture1, capture2, ...]. We keep just the captures.
input
|> then(&Regex.scan(~r/(\w+)=(\d+)/, &1))
|> Enum.map(fn [_full, key, value] -> "#{key} is #{value}" end)
|> Enum.join("; ")
|> IO.puts()
# => alice is 30; bob is 25

The ~r/.../ sigil builds a compiled regex backed by the same re/PCRE engine; Regex.scan/2 yields [full, key, value] for each match, and the |> pipe maps each into a string before Enum.join/2.

Gleam
import gleam/io
import gleam/list
import gleam/option
import gleam/regexp
import gleam/string

pub fn main() {
  let input = "alice=30, bob=25"
  let assert Ok(re) = regexp.from_string("(\\w+)=(\\d+)")

  // regexp.scan returns a List(Match); each Match's submatches are
  // Option(String), since a group might not participate in a match.
  regexp.scan(with: re, content: input)
  |> list.filter_map(fn(m) {
    case m.submatches {
      [option.Some(key), option.Some(value)] -> Ok(key <> " is " <> value)
      _ -> Error(Nil)
    }
  })
  |> string.join("; ")
  |> io.println()
}
// => alice is 30; bob is 25

Gleam's gleam/regexp also wraps PCRE, but stays type-safe: scan returns a List(Match) whose submatches are Option(String), so a case makes you handle groups that did or did not match before list.filter_map collects the successes.

LFE
(defmodule regex-text
  (export (run 0)))

(defun run ()
  (let* ((input "alice=30, bob=25")
         ;; Same PCRE engine as Erlang; capture only the groups.
         (`#(match ,pairs)
          (re:run input "(\\w+)=(\\d+)"
                  '(global #(capture all_but_first list))))
         (parts (lists:map
                  (match-lambda
                    (((list k v)) (io_lib:format "~s is ~s" (list k v))))
                  pairs)))
    (io:format "~s~n" (list (lists:join "; " parts)))))
;; (regex-text:run) => alice is 30; bob is 25

LFE calls the very same re:run/3 on the shared PCRE engine, destructuring its #(match Pairs) result with a quasiquote pattern and mapping each [Key Value] with a match-lambda before lists:join/2 joins them.

Luerl
local input = "alice=30, bob=25"

-- Lua has no PCRE; it uses lighter Lua patterns where %a means a
-- letter and %d a digit. gmatch iterates every match, returning
-- this match's captures directly.
local parts = {}
for key, value in string.gmatch(input, "(%a+)=(%d+)") do
  parts[#parts + 1] = key .. " is " .. value
end

print(table.concat(parts, "; "))
-- => alice is 30; bob is 25

Luerl runs Lua, whose string library offers Lua patterns instead of PCRE: (%a+)=(%d+) captures letters and digits, and string.gmatch is an iterator that hands back each match's captures, gathered into a table and joined with table.concat.