I just learnt Tasks, GenServers, Supervisors and it feels awesome. What else should I know?

Not much to add, but

Congratulations! Those OTP tools in particular really are the heart of what makes Elixir/erlang truly special, and mastering them is probably the largest learning curve to climb, since there’s little else like it out there in other languages!

I would second the sentiment of learning how, but also when, to use ETS. Once you have a multi-process setup using these new tools, passing data between them can become a bottleneck, and ETS is designed to circumvent this for data access in parallel, if order of operations is not important; whereas GenServer serializes the operations you perform within it.

4 Likes

Learnt :digraph, it’s incredible!

  1. Do you use it to schedule tasks?
  2. How can we rollback our changes in opposite order of the tasks done?
  3. What are some other usages?

worker_bee_tasks = :digraph.new([:acyclic])

task_closure = fn step ->
  fn ->
    IO.puts(step)
    
    # Simulate load
    Process.sleep(500)
  end
end

:digraph.add_vertex(worker_bee_tasks, :pick_nectar, task_closure.("Pick Nectar"))
:digraph.add_vertex(worker_bee_tasks, :store_nectar, task_closure.("Store Nectar"))
:digraph.add_vertex(worker_bee_tasks, :eat_nectar, task_closure.("Eat Nectar"))
:digraph.add_vertex(worker_bee_tasks, :prep_honey, task_closure.("Prep Honey"))

:digraph.add_edge(worker_bee_tasks, :pick_nectar, :store_nectar)
:digraph.add_edge(worker_bee_tasks, :pick_nectar, :eat_nectar)
:digraph.add_edge(worker_bee_tasks, :store_nectar, :prep_honey)
:digraph.add_edge(worker_bee_tasks, :eat_nectar, :prep_honey)
worker_bee_tasks
|> :digraph_utils.topsort()
|> Enum.each(fn vertex ->
  {_vertex, task} = :digraph.vertex(worker_bee_tasks, vertex)
  task.()
end)

:digraph.delete(worker_bee_tasks)

Elixir’s sage library seems like a much better approach:


P.S. Excerpt is from Elixir Patterns book!

1 Like

After learning about how GenServers can help you and when to use them, the next thing to learn is when not to use them!

There is a great concept called “functional core”. The idea is that you do the vast majority of your code in pure functional style. This is actually not too hard once you get used to the idea, and it makes testing SOOO much easier (no mocks or anything, just call functions and check the return values!).

Then at the boundary of your code, you use some GenServers to hold the top level state of your application and act as the boundary of the system. This then minimises the painful testing of stateful stuff.

This blog post talks about the pattern and links to a great book: Building a Functional Core in Elixir | by Kevin Hoffman | Medium

2 Likes

I think I’m at a similar place as you (or a little behind). One book that I really enjoyed was Tony Hammond’s Exploring Graphs with Elixir. (If you liked :digraph there is many more graphs in there.) As a self-teaching alchemist, I felt I’ve learnt a great deal just from how he setup a project (before even getting to any graphs.)

1 Like

Learnt about Collectable protocol from Elixir in Action!!


defmodule Q do
  defstruct entries: {}
  def new(), do: %Q{entries: :queue.new()}
  def enqueue(q, entry), do: %Q{entries: :queue.in(entry, q.entries)}
end

defimpl Collectable, for: Q do
  def into(original) do
    {original, &into_callback/2}
  end

  defp into_callback(q, {:cont, entry}) do
    Q.enqueue(q, entry)
  end

  defp into_callback(q, :done), do: q
  defp into_callback(_q, :halt), do: :ok
end

for i <- 1..3, into: Q.new(), do: I
# %Q{entries: {[3, 2], [1]}}

OR

1..3
|> Enum.into(Q.new())

Does anyone use this in production?

1 Like