deadbeef

deadbeef

You’ll often see the acc last, rather than first, in your example.

Totally agree.

To provide more detail, I structured it specifically to adapt for Agent.get_and_update/5, i.e. to do this:

@spec next(n :: integer) :: integer
def next(n) do
  Agent.get_and_update(__MODULE__, RLEIterator, :rle_next, [n])
end
  • I changed the name from do_next/2 to (public) rle_next/2 to distinguish against the do_* pattern. I felt it isn’t a traditional reducer/accumulator. Felt more akin to something like Map.pop/3, where I return some value alongside the “updated” data structure in a tuple (i.e. {a, state()}
  • For args :: [term()], the state is added first, i.e. the state list needs to be the first argument passed to do_next/2/rle_next/2. So [n] effectively becomes [state, n]rle_next(state, n) (similar to the recursive case).
  • Agent.get_and_update/5 (and /3) expect the return to be {a, state()}, which is why the return is in that same ordered tuple (like mentioned above, similar to the likes of Map.pop/3)

This is also a reasonable use-case for the (often discouraged in production use) process dictionary

For sure. I chose Agent since common behaviors like get_and_update are provided “for free” and just what I was more familiar with.
But Process.put/2 for init_/1 could be cleaner since I think you can call it the same the first time and subsequent times.
Imagine you could do something like this:

defmodule RLEIterator do
  def init_(encoding), do: Process.put(__MODULE__, encoding)

  def next(n) do
    Process.get(__MODULE__)
    |> rle_next(n)
    |> then(fn {val, encoding} ->
      Process.put(__MODULE__, encoding)
      val
    end)
  end
 
  def rle_next(encoding, n)
  # ...
end
0
Post #11
— All posts loaded —