JubatusのRustクライアントを書きました

[msgpack-rust](https://github.com/3Hren/msgpack-rust)と[msgpack-rpc-rust](https://github.com/euclio/msgpack-rpc-rust)を使ったライブラリの試作として、[Jubatus](http://jubat.us/en/)クライアントを書いてみました。

Rustのバージョンは1.13.0です。

現時点ではClassifierとRecommenderとにしか対応していません。


## リポジトリ

[jubatus-rust-client](https://github.com/hhatto/jubatus-rust-client)


## 使い方

Jubatusのドキュメントにある[Classifierの例](http://jubat.us/ja/tutorial/classifier.html)をRust実装で試してみます。

他のクライアントの例と同様、事前に`shogun.json`を作成し、`jubaclassifier`でサーバを起動しておきます。

まずは `cargo new` でプロジェクト作成します。

```console
$ cargo new --bin jubaclassify
$ cd jubaclassify
```

Cargo.tomlに以下を追加。`rand` crateはトレーニングデータをシャッフルするために使います。

```toml:Cargo.toml
[dependencies]
jubatus = { git = "https://github.com/hhatto/jubatus-rust-client.git" }
rand = "0.3"
```

少し長いですが、 `src/main.rs` は以下です。

```rust:main.rs
extern crate jubatus;
extern crate rand;

use jubatus::classifier::client::ClassifierClient;
use jubatus::classifier::types::LabeledDatum;
use jubatus::common::datum::Datum;
use rand::{thread_rng, Rng};


fn train(client: &mut ClassifierClient) {
    let mut train_data = vec![];
    for n in vec!["家康", "秀忠", "家光", "家綱", "綱吉", "家宣", "家継", "吉宗", "家重", "家治", "家斉", "家慶", "家定", "家茂"] {
        let mut d = Datum::new();
        d.add_string("name", n);
        train_data.push(LabeledDatum {
            label: "徳川".to_string(),
            data: d,
        });
    }
    for n in vec!["尊氏", "義詮", "義満", "義持", "義量", "義教", "義勝", "義政", "義尚", "義稙", "義澄", "義稙", "義晴", "義輝", "義栄"] {
        let mut d = Datum::new();
        d.add_string("name", n);
        train_data.push(LabeledDatum {
            label: "足利".to_string(),
            data: d,
        });
    }
    for n in vec!["時政", "義時", "泰時", "経時", "時頼", "長時", "政村", "時宗", "貞時", "師時", "宗宣", "煕時", "基時", "高時", "貞顕"] {
        let mut d = Datum::new();
        d.add_string("name", n);
        train_data.push(LabeledDatum {
            label: "北条".to_string(),
            data: d,
        });
    }

    // training data must be shuffled on online learning!
    let mut rng = thread_rng();
    rng.shuffle(&mut train_data);

    // run train
    client.train(train_data);
}

fn predict(client: &mut ClassifierClient) {
    // predict the last shogun
    for n in vec!["慶喜", "義昭", "守時"] {
        let mut d = Datum::new();
        d.add_string("name", n);
        let mut res = client.classify(vec![d]);
        // get the predicted shogun name
        let ref shogun_name = res[0].iter_mut().max_by_key(|x| (x.score * 100.) as i64).unwrap().label;
        println!("{} {}", shogun_name, n);
    }
}

fn main() {
    let host = "127.0.0.1:9199";
    let name = "test";
    let mut client = ClassifierClient::new(host, name);
    train(&mut client);
    predict(&mut client);
}
```

ビルドして実行してみます。

```console
$ cargo build
$ cargo run
徳川 慶喜
足利 義昭
北条 守時
```

無事、例の通りに実行できました。


## コード生成処理について
Jubatusクライアントは[jenerator](https://github.com/jubatus/jubatus/tree/master/tools/jenerator)と呼ばれるJubatusが提供するコードジェネレータを使用して生成されています。
jubatus-rust-clientは[jubatus/jubatus](https://github.com/jubatus/jubatus)をフォークした[hhatto/jubatusのsupport-rust-client-for-jeneratorブランチ](https://github.com/hhatto/jubatus/tree/support-rust-client-for-jenerator)を使ってコード生成しています。

現状はjubatus-rust-client向けに生成するコードは100%自動生成では無く、
一部手で修正している箇所があります。今後100%自動生成にすべく改修する予定です。 
（そもそも私がOCamlをほとんど書いたことないので、かなりゆっくりな対応になりそうですが。）


## 使用したcrateについて

RustでMessagePackを扱うcrateとしては[mneumann/rust-msgpack](https://github.com/mneumann/rust-msgpack)も存在しているのですが、1年近く変更が無くメンテナンスされてなさそうなのと、msgpack-rpc用のシリアライザ/デシリアライザが用意されてそうなのですが、通信部分が提供されていないので簡単に使えなさそうだったので、[msgpack-rust](https://github.com/3Hren/msgpack-rust)と[msgpack-rpc-rust](https://github.com/euclio/msgpack-rpc-rust)の組み合わせを選びました。

## その他改善点のまとめ
* rmp::value::Value <-> Rust構造体 それぞれの変換をもう少しエレガントにできないか？
    * 今のところ対象の構造体に `from_msgpack_value` と `to_msgpack_value`を生やしてそれを使って変換している
    * `serde` crateあたりでなんとかできそうなできなさそうな...
* `jenerator` のコード(`rust.ml`)をもう少しエレガントにできないか？
    * 力技で書いたところが多い
    * HashMapや多段のVector(`Vec<Vec<Struct>>`みたいなの)をうまく処理できるようなコードを生成できていない
    * Struct.from_msgpack_value() 内でrmp::Value::Array.as_array()使って値を順番に取り出すのだが、インデックス番号が0固定になっている
* テスト書く


## 最後に

jubatus-rust-client作ってみましたの共有と使い方の紹介でした。
Jubatusユーザのみなさん、Rustユーザのみなさん、是非とも使ってみてください。