Julia で Actor

# Actorの復習
Actorは、Curl Hewitt が1973年に提唱した計算モデルだが、2010年代になって、ScalaのAkkaで採用されるなど再度注目を集めている。元のモデルのコンセプトの本質は、私の理解するところでは

- 状態をカプセル化した主体(オブジェクト)
- 実行の主体(実行スレッド)

の2つが一体となっていることにある。通常のオブジェクト指向言語にオブジェクトと直行した形でスレッドを持ち込むと、競合状態を避けるためにオブジェクト内部の状態更新の際にロックを掛ける必要が発生する。これに対してActorモデルでは、オブジェクト内部を触れるスレッドは一つしか無いので、競合を考えずに更新できるので書きやすい。

Actorモデルでは、各Actorがメッセージボックスを持ち、そこに送られたメッセージを順次処理する。特徴はメッセージ送信が完全に非同期であることで、メッセージを送信した側は、送信し終わったら次の処理に進む。メッセージを受信した側は、メッセージを処理するだけで結果を返却することはない。結果が必要な場合には、返答用のメールボックスを用意してそこに明示的に返答するように処理を定義すればいい。


# Actorの実装
Actorモデルでは、個々のActorに実行のスレッドを割り当てることになる。これを本物のスレッドで実装してしまうとまったくスケーラブルでなくなってしまう。最近多くの言語で採用されている、コルーチンを用いるとこの心配がなくなる。

メッセージボックスは、これまた最近の言語でコルーチン間の通信に用いられるチャネルを用いれば自然に実現できる。つまり、コルーチンとチャンネルがある言語なら簡単にActorを実装できる、はず。

## Juliaでの実装
Juliaには始めからコルーチンやFutureなどの機構が用意されている。これにさらにActorを加える意味があるのか、正直わからないのだけど、とりあえずやてみよう。

当然[既存の実装](https://github.com/JuliaActors/Actors.jl)もあるのだった。まあ、しかし色々あってもいいだろう。


## インターフェイス設計
実装はともかく、どのように使えるべきなのかを考えてみよう。具体的な例がないと考えにくいので、数値を保持していて、メッセージで増減ができるようなものを考える。Pythonでオブジェクトとして書くならこんなかんじ。

```Python
class Accum:
    def __init__(self):
        self.v = 0
    def add(self, v):
        self.v += v
    def sub(self, v):
        self.v -= v
```

### Rust Actixでの書き方
Juliaのオブジェクト指向はジェネリック関数をベースにしたものなので、相性のよさそうなRustのActor実装である[actix](https://github.com/actix/actix)を参考にしてみよう。

actixでは、アクターとメッセージをそれぞれstructで定義し、メッセージに対してハンドルするメソッドを書く。

```Rust
use actix::prelude::*;

struct Add(i64);
impl Message for Add {
    type Result = i64;
}

struct Sub(i64);
impl Message for Sub {
    type Result = i64;
}

struct Accum {
    v: i64,
}

impl Actor for Accum {
    type Context = Context<Self>;
}

impl Handler<Add> for Accum {
    type Result = i64;

    fn handle(&mut self, msg: Add, _: &mut Context<Self>) -> Self::Result {
        self.v += msg.0;
        self.v
    }
}
impl Handler<Sub> for Accum {
    type Result = i64;

    fn handle(&mut self, msg: Sub, _: &mut Context<Self>) -> Self::Result {
        self.v -= msg.0;
        self.v
    }
}

#[actix::main]
async fn main() {
    // start new actor
    let addr = Accum { v: 0 }.start();

    let res0 = addr.send(Add(10)).await;
    let res1 = addr.send(Sub(2)).await;

    println!("RESULT: {}, {}", res0.unwrap(), res1.unwrap());

    System::current().stop();
}
```

ちょっと引くくらい長い。Rustは長くなりがちだとはいえ。面白いのはメッセージの型によるジェネリック関数のディスパッチを利用してメソッドを選択しているところ。`impl Handler`で、それぞれのメッセージタイプに対して、対応するハンドラを定義している。ScalaのAkkaなどはパターンマッチを明示的に書くが、その部分が隠蔽されているといえる。


また、actixは同期の返り値をサポートしているので、この例では加算の結果と減算の結果を待つようになっている。

### Juliaでの設計
とりあえず同じコンセプトで書いてみよう。

```Julia
mutable struct Accum <: Actor
    v :: Int64
end

struct Add <: Message
    v :: Int64
end
function handle(self::Accum, mes::Add, ctx::Context)
    self.v += mes.v
end  

struct Sub <: Message
    v :: Int64
end
function handle(self::Accum, mes::Sub, ctx::Context)
    self.v += mes.v
end
```
実行はこんなかんじ。

```
addr = start(Accum(0))
put!(addr, Add(10))
put!(addr, Sub(10))
```

## 実装
これが動作するように実装する。
まずは型の定義。

```
abstract type Actor end
abstract type Message end
struct Context end 
```
Contextは何に使うのかよくわからないのでからになっている。

基本的にActorに対する参照の実態は単なるチャンネルで、チャンネルの先でコルーチンがループで回っていて、メッセージを受け取って適当なメソッドを呼び出してくれるだけでいいはず。ループはこんな感じ。コルーチンでエラーが起きるとデフォルトでは何も言わずに死ぬので、例外をトラップしてエラーを出すようにしている。

```Julia
function taskLoop(cn::Channel{Message}, target::Actor, ctx::Context)
    try
        while true
            mes = take!(cn)
            handle(target, mes, ctx)
        end
    catch e
        println(e)
    end
end
```

Actorの起動はチャンネルを作って、その後ろに`taskLoop`をコルーチンで実行するだけ。

```Julia
function start(target::Actor) 
    cn = Channel{Message}(Inf)
    ctx = Context()
    @async taskLoop(cn, target, ctx)
    cn
end
```

なんとなく動いているが、メッセージとハンドラをいちいち定義するのが面倒くさい。けど、アクターは粗粒度なので、このくらい記述のコストがあっても困らないのかも。

## 次にすること
- ワーカを使って別プロセスで動くようにする。RemoteChannelに置き換えるだけで行けるはず。
- future を返せるようにする。いくらなんでも完全非同期だと使いづらそうなので。
- 性能測定
- マクロを使って書きやすくする?