読者です 読者をやめる 読者になる 読者になる

ubnt-intrepid's blog

書いてあることがブログの内容です

React + Iron を使った Web アプリケーションのテンプレートを作った

事の発端となったツイート

まぁ正直ただの妄言なのだが(そもそも Ruby on Rails を置き換えるほど影響力のあるフレームワークを作るスキルがない) ,それはそれとしてさっとWebアプリケーションを作るテンプレート的なものが欲しくなったので勢いで作ってみた.

github.com

Rust で使える Web フレームワークは色々あるが,開発体制が比較的しっかりしていそうなこと,stable でも使えることなどを考慮して Iron にした. Rocket 良さそうなんだけど nightly 前提と言われると手が出しにくいという問題がある. あと,Iron は各種ミドルウェア(セッション管理,CSRF対策など)がそれなりに充実しているのが良さそうだった.

メモ

  • フロントエンド
    • ES2015 + Babel + Webpack
    • React のみ(Reduxとかはなし)
  • サーバサイド

おわりに

正直 Web アプリケーション開発の知識も乏しい状態で作ったのでこれで必要なものがそろっているのか不安だが,その辺りは今後少しづつ追加していこうと思います…

近況

近況報告です.

Rust でブログっぽいWebアプリを作った

題意の通り,Rust でブログっぽいWebアプリケーションを作っていました. 諸々作りこみが雑なのでもう少し改善する必要があるのですが,初心を忘れないうちに備忘録がてら作業記録を残しておきます.

vcs.ubnt-intrepid.nagoya

使用した crate たち

  • iron
    Rust 用のWebフレームワークのうち最もポピュラーだと思われる. 他にも nickel, rocket などがあるが rocket は nightly 必須なのが個人的に好きくなく,nickel は何か放置されてる感が 強く不安だったので,最終的に妥協して iron になった.

    使い勝手は Python でいう Flask に近く,必要最小限のものがそろってるなぁという感じ.とはいっても Middleware がそれなりに出揃っているので,最小限のサービスを構築するのは十分可能だと思った(かかる労力のことはここでは考えていない).

    あと,iron 公式が提供している Middleware 用の crate の名前に一般的なもの(router, params など)が多く個人的には「まじかよ」という思いです…

  • diesel - Diesel
    OR マッパ/クエリビルダ用の crate. もともとこいつの使い勝手を確認しておきたいという意図で今回のプロジェクトを作成した. 基本的には公式ページの Getting started を読めば基本的な使い方は分かると思うが,まだ情報が少ないので細かいところで躓いたときに苦労しそう. マイグレーション用の SQL を自前で書く必要があったり若干面倒なところがある気もするが,その辺は慣れればいいっぽい.

あとは毎度のごとく serdeclap などにお世話になりつつ作っております. もう少し具体的に各 crate の使い方を説明しても良いのだけど,面倒になったのであとはリポジトリの方をご参照ください…

Rust/WebAssembly 勉強会に行ってきた

misoca.doorkeeper.jp

だいぶ日にちが経ってしまいましたが,名古屋でRust関係のイベントがあるということで行ってきました. 内容自体は基礎的なもの(所有権・借用まわり)でしたが,複数人で The Rust Programming Language の内容にツッコミを入れながら深めに読み進めたからか,今まで適当に感覚的に理解していたものをある程度理詰めで理解できたような気がします. フロントエンド界隈の普段 JavaScript を書いている人たちが結構混乱していて,普段使用している言語の違いでここまで理解度に差が出るのだなぁと思ったりしました(別に煽る意図はないですが…). 個人的には, C/C++ 関係の資料を併用すると良い気がします.

あと,勉強会後の飲み会で「OCaml」「SML#」などといった単語が自然に出てくるあたりさすが名古屋だなぁという感じで妙な居心地の良さを感じました(とはいってもそこまで関数型言語を使えるわけではないのですが…)

東海圏で Rust 界隈のつながりができるいい機会なので,今後も続くと良いな…

「終わっていない議論」について

良く知らんけど,「迷惑行為(クソリプ)に困っています」と言っているのに新たに迷惑行為を産んじゃダメだろう…という程度の思いです. 「技術力で勝負」という考え方は大事だと思うが,「技術力で勝負しているんだから何やっても許される」とはならんでしょう…

おわりに

本当は最近流行している某新興SNSと連携できるよう OStatus 周りを実装するところまで行きたいのだが,そこまで到達する前に力尽きてしまいそう…

特に最近は下のような感情が支配的になっているので何とかしたい

わん

mattn.kaoriya.net

上の記事を読んでいて,最後のコード例を眺めていたらできそうな気がしたので作ってみました.

github.com

やってることは wandbox-run と同じ(パクリ)ですが,serdecurl-rust を使っている分 Rust っぽい感じに出来ていると思います.

せっかく作ったので,今後は wandbox のコマンドラインインターフェースとして成熟させたいなぁなどと思っています.

Rustacean のためのリポジトリ管理ツールを作りました

何度かつぶやいたりしていましたが、READMEを書くのが面倒になったので より多くの人に使ってもらうため少し真面目に解説記事を作りました。

rhq の概要

GitHub - ubnt-intrepid/rhq: Manages your local repositories

言わずと知れた Golang 製のリポジトリ管理ツールである ghq を Rust に移植したものです。 もともと ghqrs というものを作って放置していたのですが、内部の実装に若干気に入らない点があり再度自前で実装したのが本ツールになります。 現状では ghq すべての機能を移植できているわけではないので代替というには若干役不足ですが、自分の欲しい機能は概ね実装できたので思い切って公開することにしました。

基本的に出来ることは ghq と同じような感じですが,あまり使っていない機能などは省略しています。 下のコマンド実行例を見れば雰囲気だけでも伝わると思います。

$ rhq clone ubnt-intrepid/rhq
# git clone https://github.com/ubnt-intrepid/rhq.git ~/.rhq/github.com/ubnt-intrepid/rhq と等価
$ rhq list | sk

インストール方法

現状はソースのみの配布なため、Rust および Cargo が必要になります(ビルド済みバイナリの配布は検討中です)。 すでに Rust の開発環境が整っている場合は cargo コマンドでインストールできます。

$ cargo install rhq
# 開発版を使用する場合
$ cargo install --git https://github.com/ubnt-intrepid/rhq.git

ディレクトリ構成

基本的には ghq (および Golang)と同様、ルートディレクトリ(デフォルトでは ~/.rhq)直下に ホスト名/ユーザ名/リポジトリ名 という形式でクローンしたリポジトリが格納されます. ghq では複数のルートディレクトリを指定することが出来るようになっていますが、クローン時のディレクトリの決定が面倒だったためrhq では原則として単一のルートディレクトリを用いるようにしました. ただしそれでは不便な場合もあるので,後述するようにリポジトリ探索用のディレクトリを追加するための設定項目を設けています.

  • ~/.rhq/
    • github.com/
      • user1/
        • project1/
        • project2/
      • user2/
        • project1/
    • gitlab.com/
      • user1/
        • project3/

コマンド一覧

rhq clone [<query>] [--arg=<arg>] [-n | --dry-run]

<query> に指定した文字列を元にリモートリポジトリのURLを推測し, ルートディレクトリ(デフォルトでは ~/.rhq)下の指定場所にクローンします(要は ghqghq get です)。 例えばこのプロジェクトのリポジトリを取得したい場合は次のようにします.

$ rhq clone ubnt-intrepid/rhq
# git clone https://github.com/ubnt-intrepid/rhq.git ~/.rhq/github.com/ubnt-intrepid/rhq が実行される

--arg オプションで git clone に渡すオプションを指定できます。 ghq get では --shallow オプションなどを用いてクローン時のオプションを設定しますが,より柔軟な制御をおこなえるように rhq では Git のオプションをそのまま渡す方針を取りました。

<query> を省略した場合は,標準入力から1行ずつ読みこみリポジトリのクローンを行います。

rhq list

ルートディレクトリ直下にあるリポジトリのパスを取得し、標準出力に表示します。 後述する探索用ディレクトリに vim-plug や zplug のディレクトリを指定しておくことで,(別途スクリプトを用意することなく)一括してリポジトリを管理することが可能です。

rhq foreach [-n | --dry-run] <command> [<args>...]

管理下のリポジトリディレクトリ下で指定したコマンドを実行します。 例えば、管理下のリポジトリ全てに対しリモート側の変更を同期する場合は次のように実行します。

$ rhq foreach -- git fetch --all

現状はシングルスレッドで実行するためすべてのコマンドの実行が完了するまでに時間を要します。そのうち並列しようと思ってはいますが現状では未定です…

rhq completion <shell> [<out-file>]

各種シェル用の補完スクリプトを生成します。 clap の補完スクリプト生成機能をそのまま使っているだけですが. <out-file> にはスクリプトの出力先のパスを指定し,省略した場合は標準出力にスクリプトを吐き出します.

設定ファイル

TOML 形式での設定ファイルの読み込みをサポートしています. 設定ファイルは ~/.config/rhq/config~/.rhqconfig の順番で読み込み,各項目は上書き・追記されます. 現在は以下の項目を用意しています.

root = "~/.rhq"

supplements = [
  "~/.go/src",
  "~/.vim/plugged",
  "~/.zplug/repos",
  "~/.dotfiles"
]

応用例

ghq 側の応用例がほぼそのまま使うことができます。 そのため、そこまで説明する必要はない気もしますが、はじめての方向けに簡単に紹介しておきます。

GitHub から特定ユーザのリポジトリを一括クローンする

GitHub APIコマンドラインJSONを整形できる jq を使った例です。 現状は rhq clone が並列化をしていないので,愚直に xargs で並列化したほうが良い気がしますが…

$ ghuser=ubnt-intrepid
$ curl -s "https://api.github.com/users/$ghuser/repos?max_pages=100" \
  | jq -r '.[].name' \
  | awk '{printf("'$ghuser'/%s\n",$1)}'
  | rhq clone --arg="--depth 50"

この辺 を使って直接 GitHub API を呼び出しても面白そう

シェル・テキストエディタとの連携

peco や fzf などの fuzzy selector を用いることで端末上での作業が効率化できることが知られています。 例として、Zsh 上で Rust 製の fuzzy selector である skim と連携した例を紹介します. 下のコードを ~/.zshrc などに書いておくことで, Ctrl+Gリポジトリの一覧を skim で表示し, 選択したディレクトリに移動することが出来ます.

function __fuzzy-select-repositories() {
  local selected=$(rhq list | sk --prompt='REPOS> ' --query="$LBUFFER")
  if [[ -n $selected ]]; then
    BUFFER="cd \"${selected}\""
    zle accept-line
  fi
  zle clear-screen
}

zle -N __fuzzy-select-repositories
bindkey '^g' __fuzzy-select-repositories

Vim では Unite や ctrlp を使用することが考えられます. 例えば mattn/ctrlp-ghq を使用する場合は次のような感じになります.

Plug 'mattn/ctrlp-ghq'

let g:ctrlp_ghq_command = 'rhq'
let g:ctrlp_ghq_actions = [
  \   { "label": "Open", "action": "Explore", "path": 0 },
  \ ]

" <leader>g でセレクタを起動する
noremap <Leader>g :<C-u>CtrlPGhq<CR>

Visual Studio Code への組み込みですが,せっかくなので自前で拡張機能作ってみましたVisual Studio Code Marketplace で公開しているので ext install vscode-rhq などしてインストールして下さい.

おわりに

今後の計画として、せっかくなので少しリッチな機能を用意しようかなぁと思っています…

  • rhq clone および rhq foreach の並列化
  • custom hook
  • ホストの設定

特に並列化などは良さげな並列処理のライブラリを試してみる意味でも早急に実装しようと考えています…

プルリクエストやツッコミなど、随時募集しています。

最近作った・メンテしたRustプロダクト

供養および近況報告

rustplotlib

github.com

Matplotlib の Rust バインディングが欲しいなぁと思っていたので勢いで作ったもの. Rust という言語の特徴を考えると必要となるケースはあまりない気がするが,作ってしまった以上便利なものであるとだけ言っておく.

使い方は,まず下のように Builder でグラフを構築した後,

extern crate rustplotlib;
use rustplotlib::{Figure, Axes2D, Scatter, Line2D, FillBetween};

let x: Vec<f64> = (0..40).into_iter().map(|i| (i as f64) * 0.08 * PI).collect();
let y1: Vec<f64> = x.iter().map(|x| x.sin()).collect();
let y2: Vec<f64> = x.iter().map(|x| x.cos()).collect();

let ax1 = Axes2D::new()
  .add(Scatter::new(r"$y_1 = \sin(x)$")
    .data(&x, &y1)
    .marker("o"))
  .add(Line2D::new(r"$y_2 = \cos(x)$")
    .data(&x, &y2)
    .color("red")
    .marker("x")
    .linestyle("--")
    .linewidth(1.0))
  .xlabel("Time [sec]")
  .ylabel("Distance [mm]")
  .legend("lower right")
  .xlim(0.0, 8.0)
  .ylim(-2.0, 2.0);

let ax2 = Axes2D::new()
  .add(FillBetween::new()
    .data(&x, &y1, &y2)
    .interpolate(true))
  .xlim(0.0, 8.0)
  .ylim(-1.5, 1.5);

let fig = Figure::new()
  .subplots(2, 1, vec![Some(ax1), Some(ax2)])

Figure::apply(&mut backend) を呼び出すことで実際の描画に反映させる.

use rustplotlib::Backend;
use rustplotlib::backend::Matplotlib;

let mut mpl = Matplotlib::new().unwrap();
mpl.set_style("ggplot").unwrap();

fig.apply(&mut mpl).unwrap();

mpl.savefig("simple.png").unwrap();
mpl.dump_pickle("simple.fig.pickle").unwrap();
mpl.wait().unwrap();

調子乗った名前をしている割には大した機能を実装できていないので,余裕が出来たら適宜機能を拡張していく予定…

rwm

github.com

なんかウィンドウマネージャ作ってみたくなったので作ったもの. といってもウィンドウマネージャを実装するための知識が皆無だったので,以下のサイトで紹介されている Standard ML の実装をベースに Rust に移植した.

MLでウィンドウマネージャ作成

現状はリサイズ時の処理に不具合があり,クリック場所が悪いと panic してウィンドウマネージャが終了する.

rhq

github.com

以前作成した ghqrs という ghq クローンを再度書き直したもの. ghqrs は元々 Bash on Windowsgo build が動かなくてムキーッとなって作ったという経緯があり, メンテナンスするのが面倒になった 実装の所々に気に入らない箇所が目立ったので作り直した.

おわりに

  • 生きるの辛い
  • Windows10 Creators Update で Rust が BashOnWindows でも動くようになるらしいですね(今も動いてない…?)
  • ご意見,プルリクエストなどお待ちしています

rustup を Vim から呼び出すプラグインを作りました

タイトルの通りです。 少し前に rustup のメジャーアップデートがアナウンスされたばかりですがそれとは関係ないです。

Vim のタブライン上に使用中の Rust のツールチェインを表示するように設定を弄っているときに思いつき、この手のプラグインが見つからなかったことを踏まえて勢いで作りました。

github.com

Rust 的にも Vim script 的にも特に難しいことをしていることはなく、単に関数名に対応したコマンドライン引数で rustup を実行し、得られる標準出力を文字列を返す仕様にしました。

一応セールスポイントを挙げておくと、 rustup#active_toolchain() という関数を呼び出すことで現在有効になっているツールチェインを取得することができるようになっています。

使用例として、タブラインの右端に現在使用しているチャンネルを表示するための設定を下に書いておきました(Vim まわりの知識が乏しいのであまり良い例ではないですがご了承ください)。

let s:current_rustup_toolchain = ''

function! s:rustup_toolchain()
  if s:current_rustup_toolchain !=# ''
    return s:current_rustup_toolchain
  endif
  try
    let s:current_rustup_toolchain = rustup#active_toolchain()
  catch
  endtry
endfunction

function! s:make_tabline()
  let envstatus = ''

  let toolchain = s:rustup_toolchain()
  if toolchain !=# ''
    let toolchain = split(toolchain, '-')[0]
    let envstatus = envstatus . '[rust:' . toolchain . ']'
  endif

  return '%#TabLineFill#%T%=' . envstatus
endfunction


set showtabline=2
set tabline=%!tabline#make_tabline()
augroup TabLine
  autocmd!
  autocmd VimEnter * call s:tabline()
augroup END

あまり活用する機会が無い気もしますが、Rustacean かつ Vimmer な方々はぜひお試しください。

Rust 製の汎用機械学習ライブラリ rusty-machine

本記事は Rust Advent Calendar 2016 10日目の記事です。

Rust Advent Calendar 2016 へ多くの方にご参加いただき、(一応)主催者としては嬉しい限りです。 遅くなってしまいましたが、この場を借りてお礼を申し上げます。

今回は Rust 製の汎用機械学習ライブラリである rusty-machine を紹介します。

rusty-machine

rusty-machine は Rust で書かれた汎用の機械学習ライブラリです。 100% Rust で書かれており、外部ライブラリへの依存なく使用できることが特長です。

github.com

公式の README によると、2016年12月10日現在以下の手法に対応しています。 scikit-learn など既存の機械学習ライブラリと比較するとまだ足りない機能もありますが、そのあたりは今後の発展次第でしょうか。

教師あり学習:

教師なし学習:

使用例 : 混合ガウスモデルの教師なし学習

使用例として、混合ガウスモデルによる教師なし学習のサンプルを作成しました。ただ、学習結果を表示する方法について延々と考えていたら時間切れになってしまい後半部分がごちゃごちゃになっているのでご注意ください…

extern crate csv;
extern crate rand;
extern crate rustc_serialize;
extern crate rusty_machine as rm;
extern crate rmp_serialize as msgpack;

use rand::distributions::IndependentSample;
use rm::linalg::{Vector, Matrix};
use rm::learning::gmm::{CovOption, GaussianMixtureModel};
use rm::learning::UnSupModel;

#[allow(dead_code)]
#[derive(Debug, Clone, RustcDecodable)]
struct Iris {
    sepal_length: f64,
    sepal_width: f64,
    petal_length: f64,
    petal_width: f64,
    class: i32,
}

fn load_iris_dataset() -> (Vec<Vec<f64>>, Vec<Vec<f64>>) {
    let mut reader = csv::Reader::from_file("./data/iris.csv").unwrap().has_headers(true);

    let mut dest = Vec::new();
    for row in reader.decode() {
        let row: Iris = row.unwrap();
        dest.push(vec![row.sepal_length, row.sepal_width]);
    }

    let mut rng = rand::thread_rng();
    let dist = rand::distributions::Range::new(0.0, 1.0);

    dest.iter()
        .cloned()
        .partition(|_| dist.ind_sample(&mut rng) >= 0.02)
}

fn into_matrix(data: &Vec<Vec<f64>>, size: usize) -> Matrix<f64> {
  Matrix::new(data.len(), size, data.iter().flat_map(Clone::clone).collect::<Vec<f64>>())
}

fn main() {
    // read iris dataset.
    let (train_inputs, test_inputs) = load_iris_dataset();

    // construct Gaussian mixture model.
    let mut gmm = GaussianMixtureModel::new(2);
    gmm.set_max_iters(100);
    gmm.cov_option = CovOption::Full;

    // train.
    let inputs = into_matrix(&train_inputs, 2);
    gmm.train(&inputs).unwrap();

    // calculate
    let inputs = into_matrix(&test_inputs, 2);
    let probs = gmm.predict(&inputs).unwrap();

    plot_result(train_inputs,
                test_inputs,
                probs.into_vec(),
                gmm.means().cloned().unwrap(),
                gmm.covariances().cloned().unwrap(),
                gmm.mixture_weights().clone());
}

fn plot_result(train_inputs: Vec<Vec<f64>>,
               test_inputs: Vec<Vec<f64>>,
               probs: Vec<f64>,
               means: Matrix<f64>,
               covariances: Vec<Matrix<f64>>,
               mixture_weights: Vector<f64>) {
    #[derive(RustcEncodable)]
    struct Value {
        train_inputs: Vec<Vec<f64>>,
        test_inputs: Vec<Vec<f64>>,
        probs: Vec<f64>,
        means: Vec<f64>,
        covariances: Vec<Vec<f64>>,
        mixture_weights: Vec<f64>,
    }
    let val = Value {
        train_inputs: train_inputs,
        test_inputs: test_inputs,
        probs: probs,
        means: means.into_vec(),
        covariances: covariances.into_iter().map(|c| c.into_vec()).collect(),
        mixture_weights: mixture_weights.into_vec(),
    };

    use rustc_serialize::Encodable;
    use msgpack::Encoder;
    let mut buf = Vec::new();
    val.encode(&mut Encoder::new(&mut buf)).unwrap();

    use std::io::Write;
    use std::process::{Command, Stdio};
    let mut child = Command::new("python")
        .arg("./scripts/plot.py")
        .stdin(Stdio::piped())
        .spawn()
        .unwrap();

    child.stdin.as_mut().unwrap().write_all(&buf[..]).unwrap();

    child.wait_with_output().unwrap();
}

リポジトリGitHub に公開しておきました。ちまちま更新するかもしれませんがこちらもご参照ください。

github.com

おわりに

正直 Python に比べると必要な crate がまだまだ不足しているため苦労しますが、今後の発展次第では快適に機械学習を組み込んだアプリケーションを作成することができると感じました(雑)。

以上、Rustアドベントカレンダー 10日目の記事でした。