So I’m writing an Elixir parser that takes a << binary >>
and converts it into a structure. Part of the protocol
is such that the same parsing repeats a number of times, based on a previous field. In a non-functional language
like GO, a simple for loop would be the best choice (parse and consume)…
Here are 3 ways I implemented this.
Loop with function and pattern matching
This is the first one I could think of. We define a function and use pattern matching on arguments to break out of recursion.
The first defined variant has 0
as its repeat couinter. The second actually does the consuming.
First taking 32 bits from rest
and then recursing into itself by adding to the units
list.
Remember Elixir tail recursion and tail-call optimisation!
defp parse_unit(0, units, << rest:: binary >>) do
{:ok, units, rest}
end
defp parse_unit(repeat, units, << rest:: binary >>) do
<< u :: 32, rest::binary >> = rest
parse_unit(repeat - 1 , units ++ [u], rest)
end
We use this helper function like this. It will do num
repeats, fill the empty list and consume rest
.
{:ok, units, rest} = parse_unit(num, [], rest)
Using anonymous functions
The same thing, but defining the function locally as an anonymous function. Uglyer, but its close to the code where used.
parse_unit2 = fn
_, 0, units, << rest:: binary >> -> {:ok, units, rest}
f, repeat, units, << rest :: binary >> ->
<< u :: 32, rest::binary>> = rest
f.(f, repeat - 1 , units ++ [u], rest)
end
{:ok, units, rest} = parse_unit2.(parse_unit2, num, [], rest)
Using Enum.reduce
Then I tried one of the Enum functions. Here I use the generated list 1..num
to releat
and the accumulator of Enum.reduce/3 to carry the consumable « binary » and the result.
{units, rest} = Enum.reduce(1..num, {[], rest}, fn _, {units, rest} ->
<< u :: 32, rest::binary>> = rest
{ units ++ [u], rest }
end)
Elixir is nuts. I like Elixir.