Moon soon
Moon soon

Reputation: 2856

no function clause matching in Agent.start_link/2

I wanna to try to start Agent in Supervisor with one line for test purpose:

iex(1)> {:ok, pid} = Supervisor.start_link([{Agent, [fn -> 42 end, name: :test]}], strategy: :one_for_one)

** (EXIT from #PID<0.115.0>) evaluator process exited with reason: shutdown: failed to start child: Agent
    ** (EXIT) an exception was raised:
        ** (FunctionClauseError) no function clause matching in Agent.start_link/2
            (elixir) lib/agent.ex:215: Agent.start_link([#Function<20.99386804/0 in :erl_eval.expr/5>, {:name, :test}], [])
            (stdlib) supervisor.erl:365: :supervisor.do_start_child/2
            (stdlib) supervisor.erl:348: :supervisor.start_children/3
            (stdlib) supervisor.erl:314: :supervisor.init_children/2
            (stdlib) gen_server.erl:365: :gen_server.init_it/2
            (stdlib) gen_server.erl:333: :gen_server.init_it/6
            (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

Please notice Supervisor.start_link([{Agent, [fn -> 42 end, name: :test]}], strategy: :one_for_one), This code throw error to me, Is it problems with syntax?

I am using Elixir 1.5

Upvotes: 1

Views: 1100

Answers (1)

Justin Wood
Justin Wood

Reputation: 10061

The reason this is happening is because the type for Supervisor.start_link/2 is start_link([:supervisor.child_spec | {module, term} | module], options) :: on_start

With how you are passing in data, it looks like you are trying to use {module, term}. That term is what will be passed to the first argument for module's start_link/2 function. So, the equivalent of what you were trying to do is Agent.start_link([fn -> 42 end, name: :test], []) (you can see this in the error). You are passing everything into the first argument of the function which, in this case, does not allow for taking a list.

You are going to want to use the :supervisor.child_spec path. Something along the lines of the following should work for you.

import Supervisor.Spec

children = [
  worker(Agent, [fn -> 42 end, name: :test])
]

opts = [strategy: :one_for_one]
Supervisor.start_link(children, opts)

EDIT

You just mentioned in a comment that you are using Elixir 1.5.

You are going to want to create a module that wraps the behaviour of an Agent.

defmodule MyAgent do
  use Agent

  def add(pid, n) do
    Agent.update(pid, fn state -> state + n end)
  end

  def subtract(pid, n) do
    Agent.update(pid, fn state -> state - n end)
  end

  def start_link(fun, opts) do
    Agent.start_link(fun, opts)
  end

  # This function is important because it is how we know how to pass the various options to the actual start_link function.
  def child_spec([fun | rest]) do
    %{
      id: __MODULE__,
      # The fun and rest here are the function we pass in and then the options. Feel free to do this how you want.
      start: {__MODULE__, :start_link, [fun, rest]},
      restart: :permanent,
      shutdown: 5000,
      type: :worker
    }
  end
end

Now calling Supervisor.start_link([{My Agent, [fn -> 42 end, name: :test]}], strategy: :one_for_one) should return {:ok, pid}.

Upvotes: 2

Related Questions