This is a very well prepared proposal. I have one question and one further 
proposal to improve it.

By removing the application callbacks we remove the primary place of injecting 
dynamic configuration into an application. Dynamic configuration is already a 
major hurdle, without this proposal or with it, but I'm afraid this will only 
worsen the situation. It's difficult (and inconsistent in releases vs running 
with mix) to configure applications with dynamic values - be it from 
environment variables or external services like zookeeper. Is there a planned 
proposal to improve upon this (and I should stop thinking about it) or are the 
application callbacks still the best place for this? If so, how do we make it 
more obvious?
This is mostly an open question and I don't have a good answer for today, but 
I'd like to know where we stand.

Now let's see, how I think the proposal could be improved.

How would an example GenServer callback module will look like (without the 
proposed "automatic child_spec" and with the new GenServer proposal [1]):

defmodule MyServer do
  @behaviour GenServer

  def child_spec(_args) do
    %{id: MyServer, start: {__MODULE__, :start_link, []}, restart: :transient}
  end

  def start_link(args) do
    GenServer.start_link(__MODULE__, args, args)
  end

  def init(args) do
    {:ok, new_state(args)}
  end

  # ...
end

I can see at least three issues here:

* the __MODULE__ part feels repetitive - there should be a way to specify it 
only in one place.
* we need to know if the behaviour should be a worker or a supervisor child.
* in practice it might be hard to distinguish what are the arguments to the 
callback itself, and what are the arguments to the behaviour (e.g. name). Now I 
need to understand the flow of the arguments through 3 functions, instead of 
two. Where before there was only start_link and init (and it was sometimes 
already confusing), now we have child_spec, start_link and init - even more 
confusion awaits.

The "automatic child_spec" part of the proposal addresses the first and second 
issues, but unfortunately it introduces a lot of complexity and additional 
macro "magic". With the current work in GenServer to migrate away from 
overridable functions, I'm rather surprised by this. Additionally this does not 
address the third, even more important issue. Hiding things behind macros is 
not reducing the complexity - even worse it is hiding it. It makes defining the 
module "simple", but unfortunately not "easy"[2].

I propose we embrace the child_spec fully and use it to replace start_link 
entirely. Instead of providing a raw map in child_spec, we would call a 
child_spec function in the parent behaviour, delegating the responsibility of 
knowing if this should be a "supervisor" or "worker" similar to the "automatic 
child_spec" proposal, which was one of the main issues it embarked to solve. 
Additionally we could define the child spec in a way that does not require the 
start_link function.

How would a module implemented with this look like?

defmodule MyServer do
  @behaviour GenServer

  def child_spec(_args) do
    GenServer.child_spec(__MODULE__, args)
  end

  def init(args) do
    {:ok, new_state(args)}
  end
  # ...
end

To define a child supervisor, we could use:

Supervisor.child_spec([MyServer.child_spec([])], strategy: :simple_one_for_one, 
name: MySup)

Notice how the start_link function is gone. It is in no way more complex than 
the current status quo - there are still only two functions, but we don't need 
to know if the process is a worker or a supervisor. Additionally, there's only 
one place, where we define what module the behaviour will be using. 
Furthermore, it addresses the issue of defining a "start_link" function - we 
never call it explicitly, it's not a callback of any behaviour, and seems quite 
random as a name (the BIF to start a process is called spawn_link). Where is it 
coming from? You need to know that start_link is this special snowflake of a 
function. In this proposal we explicitly call the child_spec function in the 
supervisor, and directly enter the behaviour's callbacks. It's very clear to 
trace the path of the code and understand what is happening - in the supervisor 
we call the MyServer.child_spec/1 function, which calls the 
GenServer.child_spec/2, which causes us to enter the behaviour with init/1 - 
there are no hidden defaults anywhere, no magic function names, and no macros 
(!). We should also consider, requiring calling the child_spec function 
directly in the children specification, instead of defaulting to calling 
child_spec([]) - it will have similar discoverability issues that current 
start_link has, and is only slightly more verbose.

[1] https://github.com/elixir-lang/elixir/pull/5676
[2] https://www.infoq.com/presentations/Simple-Made-Easy


-- 
You received this message because you are subscribed to the Google Groups 
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/851e8f39-19da-4749-8437-f848434d2877%40Spark.
For more options, visit https://groups.google.com/d/optout.

Reply via email to