README.md

# ExActor

Simplified implementation and usage of `gen_server` based actors in Elixir.
This library is inspired by (though not depending on) [GenX](https://github.com/yrashk/genx), but in addition, removes some more boilerplate, and changes some semantics of the handle_call/cast responses.

If you're new to Erlang, and are not familiar on how gen_server works, I strongly suggest you learn about it first. It's really not that hard, and you can use [Elixir docs](http://elixir-lang.org/docs/stable/elixir/GenServer.html) as the starting point. Once you're familiar with gen_server, you can use ExActor to make your actors (gen_servers) more compact.

Status: I use it in production.

Online documentation is available [here](http://sasa1977.github.io/exactor/).

The stable package is also available on [hex](https://hex.pm/packages/exactor).

## Basic usage

Be sure to include a dependency in your `mix.exs`:

```elixir
deps: [{:exactor, "~> 0.4.0"}, ...]
```


```elixir
defmodule Actor do
  use ExActor.GenServer

  definit do: initial_state(some_state)

  defcast inc(x), state: state, do: new_state(state + x)

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

  definfo :some_message, do: ...
end

# initial state is set to start argument
{:ok, act} = Actor.start(1)
Actor.get(act)         # 1

Actor.inc(act, 2)
Actor.get(act)         # 3
```

## Predefines

A predefine is an ExActor mixin that provides some default implementations for
`gen_server` callbacks. Following predefines are currently provided:

* `ExActor.GenServer` - All `gen_server` callbacks are provided by GenServer from Elixir standard library.
* `ExActor.Strict` - All `gen_server` callbacks are provided. The default implementations for all except `code_change` and `terminate` will cause the server to be stopped.
* `ExActor.Tolerant` - All `gen_server` callbacks are provided. The default implementations ignore all messages without stopping the server.
* `ExActor.Empty` - No default implementation for `gen_server` callbacks are provided.

It is up to you to decide which predefine you want to use. See online docs for detailed description.
You can also build your own predefine. Refer to the source code of the existing ones as a template.

## Singleton actors

```elixir
defmodule SingletonActor do
  # The actor process will be locally registered under an alias
  # provided in export option.
  use ExActor.GenServer, export: :some_registered_name

  # you can also use via, and global
  # use ExActor.GenServer, export: {:global, :some_registered_name}
  # use ExActor.GenServer, export: {:via, :gproc, :some_registered_name}

  defcall get, state: state, do: reply(state)
  defcast set(x), do: new_state(x)
end

SingletonActor.start
SingletonActor.set(5)
SingletonActor.get
```

## Handling of return values

```elixir
definit do: initial_state(arg)                      # sets initial state
definit do: {:ok, arg}                              # standard gen_server response

defcall a, state: state, do: reply(response)        # responds 5 but doesn't change state
defcall b, do: set_and_reply(new_state, response)   # responds and changes state
defcall c, do: {:reply, response, new_state}        # standard gen_server response

defcast c, do: noreply                              # doesn't change state
defcast d, do: new_state(new_state)                 # sets new state
defcast f, do: {:noreply, new_state}                # standard gen_server response

definfo c, do: noreply                              # doesn't change state
definfo d, do: new_state(new_state)                 # sets new state
definfo f, do: {:noreply, new_state}                # standard gen_server response
```

## Simplified starting

```elixir
Actor.start                           # same as Actor.start(nil)
Actor.start(init_arg)
Actor.start(init_arg, options)

Actor.start_link                      # same as Actor.start_link(nil)
Actor.start_link(init_arg)
Actor.start_link(init_arg, options)
```

### Dynamic registration

```elixir
Actor.start(init_arg, name: :some_registered_name, ...)                   # registers locally
Actor.start(init_arg, name: {:local, :some_registered_name}, ...)         # registers locally
Actor.start(init_arg, name: {:global, :some_registered_name}, ...)        # registers globally
Actor.start(init_arg, name: {:via, :gproc, :some_registered_name}, ...)   # registers via external module

# same for start_link
```

Starter functions are overridable. You can optionally specify that you don't want them:

```elixir
  use ExActor.GenServer, starters: false
```

## Simplified initialization

```elixir
# define initial state
use ExActor.GenServer, initial_state: HashDict.new

# alternatively as the function
definit do: HashSet.new

# using the input argument
definit x do
  x + 1
  |> initial_state
end
```

## Handling messages

```elixir
definfo :some_message, do:
definfo :another_message, state: ..., do:
```

## Pattern matching

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

definit :something, do: ...
definit x, when: ..., do: ...

definfo :msg, state: {...}, when: ..., do: ...
```

Note: all call/cast matches take place at the `handle_call` or `handle_cast` level. The interface function simply passes the arguments to appropriate `gen_server` function. Consequently, if a match fails, the server will crash.

## Skipping interface funs

```elixir
# interface fun will not be generated, just handle_call clause
defcall unexported, export: false, do: :unexported
```

## Using from

```
defcall a(...), from: {from_pid, ref} do
  ...
end
```

## Runtime friendliness

May be useful if calls/casts simply delegate to some module/functions.

```elixir
defmodule DynActor do
  use ExActor.GenServer

  for op <- [:op1, :op2] do
    defcall unquote(op), state: state do
      SomeModule.unquote(op)(state)
    end
  end
end
```


## Simplified data abstraction delegation

Macro `delegate_to` is provided to shorten the definition when the state is implemented as a functional data abstraction, and operations simply delegate to that module. Here's an example:

```elixir
defmodule HashDictActor do
  use ExActor.GenServer
  import ExActor.Delegator

  delegate_to HashDict do
    init
    query get/2
    trans put/3
  end
end
```

This is equivalent of:

```elixir
defmodule HashDictActor do
  use ExActor.GenServer

  definit do: HashDict.new

  defcall get(k), state: state do
    HashDict.get(state, k)
  end

  defcast put(k, v), state:state do
    HashDict.put(state, k, v)
    |> new_state
  end
end
```

You can freely mix `delegate_to` with other macros, such as `defcall`, `defcast`, and others.