ExActor.Operations

Macros that can be used for simpler definition of GenServer operations such as casts or calls.

Source

Summary

defabcast(req_def, options \\ [], body \\ [])

Defines an abcast operation

defabcastp(req_def, options \\ [], body \\ [])

Same as defabcast/3 but the interface function is private

defcall(req_def, options \\ [], body \\ [])

Defines the call callback clause and the corresponding interface fun

defcallp(req_def, options \\ [], body \\ [])

Same as defcall/3 but the interface function is private

defcast(req_def, options \\ [], body \\ [])

Defines the cast callback clause and the corresponding interface fun. See defcall/3 for more details

defcastp(req_def, options \\ [], body \\ [])

Same as defcast/3 but the interface function is private

defhandlecall(req_def, options \\ [], body \\ [])

Similar to defcall/3, but generates just the handle_call clause, without creating the interface function

defhandlecast(req_def, options \\ [], body \\ [])

Similar to defcast/3, but generates just the handle_call clause, without creating the interface function

defhandleinfo(msg, opts \\ [], body)

Defines the info callback clause. Responses work just like with casts

definit(arg \\ {:_, [], ExActor.Operations}, opts)

Similar to defstart/3 but generates just the init clause

defmulticall(req_def, options \\ [], body \\ [])

Defines a multicall operation

defmulticallp(req_def, options \\ [], body \\ [])

Same as defmulticall/3 but the interface function is private

defstart(arg1, opts \\ [], body \\ [])

Defines the starter function and initializer body

defstartp(arg1, options \\ [], body \\ [])

Same as defstart/2 but the interface function is private

Macros

defabcast(req_def, options \\ [], body \\ [])

Defines an abcast operation.

defabcast my_request(x, y), do: ...

...

# If the process is locally registered via `:export` option
MyServer.my_request(2, 3)
MyServer.my_request(nodes, 2, 3)

# The process is not locally registered via `:export` option
MyServer.my_request(:local_alias, 2, 3)
MyServer.my_request(nodes, :local_alias, 2, 3)

Request format is the same as in defcall/3

Source
defabcastp(req_def, options \\ [], body \\ [])

Same as defabcast/3 but the interface function is private.

Source
defcall(req_def, options \\ [], body \\ [])

Defines the call callback clause and the corresponding interface fun.

defcall operation, do: reply(response)
defcall get, state: state, do: reply(state)
defcall inc(x \\ 1), state: state, do: set_and_reply(state + x, response)

Request format (passed to handle_call/3)

  • no arguments -> :my_request
  • one arguments -> {:my_request, x}
  • more arguments -> {:my_request, x, y, ...}

Timeout

defcall long_call, state: state, timeout: :timer.seconds(10), do: ...

You can also make the timeout parameterizable

defcall long_call(...), timeout: some_variable, do: ...

This will generate the interface function as:

def long_call(..., some_variable)

where some_variable will be used as the timeout in GenServer.call. You won't have the access to this variable in your body though, since the body specifies the handler function. Default timeout value can also be provided via standard \\ syntax.

Pattern matching

defcall a(1), do: ...
defcall a(2), do: ...
defcall a(x), when: x > 1, do: ...
defcall a(x), state: 1, do: ...
defcall a(_), state: state, do: ...

Details

A single defcall (or similar) construct usually defines a clause for two functions: the interface function and the handler function. For example:

defcall foo(x, y), state: state, do: ...

will generate:

def foo(pid, x, y), do: ...
def handle_call({:foo, x, y}, _, state), do: ...

If you're writing multi-clauses, the following rules apply:

  • Arguments are pattern-matched in interface and in handler function.
  • The when: clause applies only to the interface function.
  • The state argument is pattern-matched in the handler function.

ExActor will try to be smart to some extent, and defer from generating the interface clause if it's not needed.

For example:

defcall foo(_, _), state: nil, do: ...
defcall foo(x, y), state: state, do: ...

will generate only a single interface function that always matches its arguments and sends them to the server process. There will be of course two handle_call clauses.

You can freely mix this with argument pattern matches:

defcall foo(1, 2), ...
defcall foo(x, y), when: x > y, ...
defcall foo(_, _), state: nil, do: ...
defcall foo(x, y), state: state, do: ...

The example above will generate three interface clauses:

  • def foo(1, 2)
  • def foo(x, y) when x > y
  • def foo(x, y)

Of course, there will be four handle_call clauses, each with the corresponding body provided via do option.

Separating interface and handler clauses

If you want to be more explicit about pattern matching, you can use a body-less construct:

defcall foo(x, y)

You can freely use multiple defcall body-less clauses if you need to pattern match arguments.

To generate handler clauses you can use defhandlecall/3:

defhandlecall foo(_, _), state: nil, do: ...
defhandlecall foo(x, y), state: state, do: ...

This approach requires some more typing, but it's more explicit. If you need to perform complex combinations of pattern matches on arguments and the state, it's probably better to use this technique as it gives you more control over what is matched at which point.

Also, this approach allows you to use the state in the when option (guard):

defcall a(x)

defhandlecall a(x), state: state, when: state > 0, do: ...
defhandlecall a(x), state: state, do: ...
Source
defcallp(req_def, options \\ [], body \\ [])

Same as defcall/3 but the interface function is private.

Can be useful when you need to do pre/post processing in the caller process.

def exported_interface(...) do
  # do some client side preprocessing here
  my_request(...)
  # do some client side post processing here
end

# Not available outside of this module
defcallp my_request(...), do: ...
Source
defcast(req_def, options \\ [], body \\ [])

Defines the cast callback clause and the corresponding interface fun. See defcall/3 for more details.

Source
defcastp(req_def, options \\ [], body \\ [])

Same as defcast/3 but the interface function is private.

Can be useful when you need to do pre/post processing in the caller process.

def exported_interface(...) do
  # do some client side preprocessing here
  my_request(...)
  # do some client side post processing here
end

# Not available outside of this module
defcastp my_request(...), do: ...
Source
defhandlecall(req_def, options \\ [], body \\ [])

Similar to defcall/3, but generates just the handle_call clause, without creating the interface function.

Source
defhandlecast(req_def, options \\ [], body \\ [])

Similar to defcast/3, but generates just the handle_call clause, without creating the interface function.

Source
defhandleinfo(msg, opts \\ [], body)

Defines the info callback clause. Responses work just like with casts.

defhandleinfo :some_message, do: ...
defhandleinfo :another_message, state: ..., do:
Source
definit(arg \\ {:_, [], ExActor.Operations}, opts)

Similar to defstart/3 but generates just the init clause.

Note: keep in mind that defstart wraps arguments in a tuple. If you want to handle defstart start(x), you need to define definit {x}

Source
defmulticall(req_def, options \\ [], body \\ [])

Defines a multicall operation.

defmulticall my_request(x, y), do: ...

...

# If the process is locally registered via `:export` option
MyServer.my_request(2, 3)
MyServer.my_request(nodes, 2, 3)

# The process is not locally registered via `:export` option
MyServer.my_request(:local_alias, 2, 3)
MyServer.my_request(nodes, :local_alias, 2, 3)

Request format is the same as in defcall/3. Timeout option works just like with defcall/3.

Source
defmulticallp(req_def, options \\ [], body \\ [])

Same as defmulticall/3 but the interface function is private.

Source
defstart(arg1, opts \\ [], body \\ [])

Defines the starter function and initializer body.

# defines and export start/2
defstart start(x, y) do
  # runs in init/1 callback
  initial_state(x + y)
end

# defines and export start_link/2
defstart start_link(x, y) do
  # runs in init/1 callback
  initial_state(x + y)
end

You can also provide additional GenServer options via :gen_server_opts option.

defstart start(x, y), gen_server_opts: [spawn_opts: [min_heap_size: 10000]], do: ...

If you need to set GenServer options at runtime, use gen_server_opts: :runtime and then the starter function will receive one more argument where you can pass options:

defstart start(x, y), gen_server_opts: :runtime do
  ...
end

...

MyServer.start(x, y, name: :foo, spawn_opts: [min_heap_size: 10000])

Body can be omitted. In this case, just the interface function is generated. This can be useful if you want to define both start and start_link:

defstart start(x, y)
defstart start_link(x, y) do
  # runs for both cases
end

Keep in mind that generated info/1 matches on the number of arguments, so this won't work:

defstart start_link(x)
defstart start_link(x, y) do
  # doesn't handle start_link(x)
end

If you want to handle various versions, you can just define start heads without the body, and then use defhandleinfo/2 or just implement handle_info/1.

Other notes

  • If the export option is set while using ExActor, it will be honored in starters.
  • You can use patterns in arguments. Pattern matching is performed in the interface function. For each specified clause, there will be one corresponding interface function clause.
  • You can provide additional guard via :when option. The guard applies to the init/1.

Request format (arg passed to init/1)

  • no arguments -> nil
  • one arguments -> {x}
  • more arguments -> {x, y, ...}
Source
defstartp(arg1, options \\ [], body \\ [])

Same as defstart/2 but the interface function is private.

Can be useful when you need to do pre/post processing in the caller process.

defmodule MyServer do
  def start_link(x, y) do
    ...

    do_start_link(x, y)

    ...
  end

  defstartp do_start_link(x, y), link: true do
    ...
  end
end
Source