There are two proposals here; the second one is at the end and is independent
of the first.
For example, unescape in elixir_interpolation.erl, or URI.decode_www_form,
which builds an intermediate string:
def decode_www_form(string) when is_binary(string), do: unpercent(string, "", true)
But if there is no percent sign, we don't need to traverse the string at all:
def decode_www_form(string) when is_binary(string) do
case :binary.match(string, "%") do
:nomatch -> string
_ -> unpercent(string, "", true)
end
end
Proposal 1: a fast path
Two things fall out of this:
- we can return the original binary untouched when there is no percent sign,
skipping the traversal entirely
- :binary.match returns the position of the first match, so when there is one,
we can copy everything up to it into the accumulator in one go instead of
byte by byte
This is local to each function and doesn't require any new machinery.
Proposal 2: precompiled patterns in persistent_term
:binary.match has to compile the pattern on every call. We could pass a
precompiled one instead, but it then has to be either a parameter to the
function or stored somewhere. One such place is persistent_term. Elixir
already stores a few things there, so I'd like to precompile the common
patterns on application start and look them up:
:binary.match(string, :persistent_term.get({:precompiled_pattern, :percent}))
This is something only Elixir can do for its own call sites - a library can
already put its own compiled patterns in persistent_term, but nobody outside
core can change what URI.decode_www_form matches against.
I'd like to hear your opinion on both.