Author: pan

Algebraic Effects for Rust

Algebraic Effects for Rust

この記事はKCS Advent Calender 18日目の記事です!
Algebraic Effectsが最近話題ですね!自分は普段Rustというプログラミング言語を使っているのですが,残念ながら(?)Algebraic Effectsの言語レベルサポートはありません.無ければ作るとも言いますし,AEをサポートするライブラリを作ってみたので紹介します.

Algebraic Effectsとは?

自分も良く分かりません.教えてください.

「継続がとれる例外」らしいですね.

どうやって実装したの?

びしょ〜じょさんの記事によると,コルーチンを使うとAEの実装ができるようです.ちょうどRustにはasync/awaitサポートのためにコルーチンが今年入ったので,それを使えば実装できそうですね.詳しい実装方法は次のコミケ(30日日曜日です!)の冊子にこれから書く書いたのでそちらを参考にしてください!

はじめてのAlgebraic Effects

以下で登場するコードはhttps://github.com/pandaman64/hello-effからもダウンロードすることができます.

まずはRustを入手します.新しい機能を利用しているので(特にunsized_locals)最新のNightlyビルドが必要です.

$ rustup update
$ rustup toolchain install nightly

それが済んだら新しいプロジェクトを作りましょう.

$ cargo new hello-eff
$ cd hello-eff

Cargo.tomlのdependenciesに次の行を追加します.まだcrates.ioにはアップロードしていないのでGitHubのURLを書いています.

eff = { git = "https://github.com/pandaman64/effective-rust.git" }

それではmain.rsを下のように書いていきましょう.

#![feature(generators)]

use eff::*;

struct Hello;
impl Effect for Hello {
    type Output = String;
}

struct World;
impl Effect for World {
    type Output = String;
}

fn main() {
    let with_effect = eff! {
        let hello = perform!(Hello);
        let world = perform!(World);

        format!("{} {}!", hello, world);
    };

    run(with_effect, |x| println!("{}", x), handler! {
        H @ Hello[_] => {
            resume!("Hello".into());
        },
        W @ World[_] => {
            resume!("World".into());
        }
    });
}

これをcargo runで実行すると

Hello, World!

と表示されることでしょう.

これは一体何が起きているのでしょうか.
コードを一行一行見ていきましょう.

#![feature(generators)]

これは,このモジュールではジェネレータを使うという宣言です.ジェネレータとはコルーチンのRustでの名称です.Rustでは実験的な機能は明示的にオプトインしなければ使うことができません(feature gateと呼びます).このライブラリはジェネレータをフルに活用しているので,この宣言が必要です.

use eff::*;

次の行はライブラリのインポートです.自分が書いたAEライブラリはeffという名前で公開されているので,eff::*と書くことによって後ろのeff!handlehandler!といった関数・マクロをインポートします.

さて,この後に続くのがエフェクトの宣言です.

struct Hello;
impl Effect for Hello {
    type Output = String;
}

ここでは,Helloという名のエフェクトを宣言しています.effではエフェクトの宣言はEffectトレイトを実装することで行います.Effectトレイトの実装にはOutput型を指定する必要があり,Outputはこのエフェクトが解決したときにどの型の値になるかに相当します.

Worldの方も同様にエフェクトの宣言がされています.

それでは,main()の中を見ていきましょう.まずは以下の部分です.

let with_effect = eff! {
    let hello = perform!(Hello);
    let world = perform!(World);

    format!("{} {}!", hello, world);
};

ここでは,eff!マクロを使ってエフェクト付きの計算を定義しています.注意してほしいのは,この時点ではまだeff!内部の計算は実行されていないということです.eff!の中では,perform!を使ってエフェクトを発動することができます.perform!式の結果は上で紹介したEffectトレイトのOutput型となります.
今回の場合はどちらもStringですね.perform!によって取得した値はformatの行のように自由に使うことができます.

eff!で定義したエフェクト付きの計算はrun関数によって実行することができます.

run(with_effect, |x| println!("{}", x), handler! {
    H @ Hello[_] => {
        resume!("Hello".into());
    },
    W @ World[_] => {
        resume!("World".into());
    }
});

run関数は
1. エフェクト付き計算
2. value handler
3. effect handler

の3つを引数にとります.1のエフェクト付き計算は上で紹介したeff!マクロで作った値です.2のvalue handlerはeff!マクロ内の最終的な計算結果を受け取ってあれこれする関数です.今回は|x| println!("{}", x)と標準出力にプリントしてますが,そのままの値が欲しい場合は|x| xとすれば良いでしょう.3のeffect handlerにエフェクトに応じて処理を行うコードを記述します.

effect handlerはhandler!マクロにハンドラを並べることで定義します.一つ一つのハンドラは

ユニークな識別子 @ エフェクトの型 [ パターン ] => 式

という文法で記述します.ハンドラを複数書くときは,カンマで区切って書きます(実装をサボっているので末尾カンマは許容されません).

エフェクトの型によってこのハンドラがどのエフェクトをハンドルするのかを指定し,ハンドルした結果がとなります.perform!に渡されたエフェクトはパターンによって束縛されます.今回の例ではHelloWorldといったエフェクトの型が重要で,値自体は不要なので_パターンによって捨てています.ユニークな識別子は実装上の都合(Rustのマクロは識別子を生成できない)で必要です.handler!内でユニークになるよう名前をつけてください.

さて,ハンドル結果のについて見ていきましょう.ここには任意の式を書くことができますが,その中でも特別に扱われるのがresume!マクロです.resume!(式)はエフェクトの発動時点(perform!の時点)から処理を再開します.このとき,perform!の結果はresume!に渡した引数に評価されます.ですので,resume!に渡す式は対応するエフェクトのOutput型の値でなければいけません.これによって,例えば実装の分離ができることでしょう.

ハンドラ内でresume!を行わない場合は,ハンドラの式の結果がrun関数の結果となります.これを使えば,例外のような大域脱出が実装できます.

また,effライブラリはハンドラのexhaustiveness checkを行います.つまり,ハンドラがエフェクト全てを網羅しているかをチェックします.試しに上のコードからWorldのハンドラを削除するとコンパイルエラーとなることでしょう(マクロの内部でエラーが発生するのでエラー自体は読んでも意味が分からないと思います...).

引数をとるエフェクト

エフェクトは引数をとることができます.どうするのかというと,Effectトレイトを実装する型にフィールドを加えるだけです.上のサンプルに下のエフェクト型を追加しましょう.

struct Ask {
    prompt: String
}
impl Effect for Ask {
    type Output = String;
}

次に,eff!部分を下のように置き換えます.Worldエフェクトの代わりにAskエフェクトをperform!するようにしました.

let with_effect = eff! {
    let hello = perform!(Hello);
    let name = perform!(Ask {
        prompt: "What's your name?".into()
    });

    format!("{} {}!", hello, name)
};

さて,扱うエフェクトの型が変わったのでハンドラも書き換えなければいけません.ここでは次のようにしました.

use std::io::{stdin, stdout, Write};
let stdin = stdin();
run(with_effect, |x| println!("{}", x), handler! {
    H @ Hello[_] => {
        resume!("Hello".into());
    },
    A @ Ask[Ask { prompt }] => {
        print!("{} ", prompt);
        stdout().flush().unwrap();

        let mut name = String::new();
        match stdin.read_line(&mut name) {
            Ok(_) => resume!(name.trim().into()),
            Err(_) => eprintln!("failed to read"),
        }
    }
});

Worldのハンドラの代わりにAskのハンドラが追加されています.Askハンドラではpromptをエフェクトから取り出した後(ここで構造体パターンが使われていることに注意),それを表示しユーザからの入力を待ちます.入力が成功した場合には,resume!によって処理を戻します(trimは末尾の改行文字を除くためです).失敗した場合にはfailed to readと標準エラーに出力して終了します.こっそりstdinをハンドラ外の環境から引っ張ってきているのにも注目してください.

error: recursion limit reached while expanding the macro

というエラーが出た場合には#![recursion_limit="128"]という行を先頭に追加してください.

制約

  • ジェネリックなハンドラ.実装上の都合でジェネリックなハンドラが宣言できません.特に不便な点は,ハンドラで参照をとることができません(参照のライフタイムが宣言できないため).
  • その他にもライフタイムの不必要な'static制約がいくつかあります.困ったときはとりあえず参照を使うのをやめてください.
    eff!のネスト.実装のアイデアはあるのでもう少しお待ちください.
  • エフェクトのうち一部だけハンドルするハンドラ.exhaustivenessとこれを両立する実装を考えているところです.型レベル黒魔術が必要かも?

以上,Rust向けAlgebraic Effectsライブラリeffの紹介でした.自分自身良くわからないままやっているので,もっと良くなるところもあると思います.質問・意見等々ある人はhttps://github.com/pandaman64/effective-rustにIssueを立てるかTwitterで@__pandaman64__までぜひメンションを飛ばしてください!

再度の宣伝ですが,KCSはC95の二日目(30日)に同人誌を頒布予定です!
自分もそちらにこのライブラリの仕組みを解説する記事を寄稿しているのでぜひそちらもご覧ください.

wpa_supplicantを使ってkeiomobile2に接続する

この記事について

最近NixOSを使っていて,wpa_supplicantというCLIソフトウェアで無線接続を管理しています.
普通のWi-Fi(PSK方式)ならwpa_passphraseを使えば接続用の設定を自動生成してくれるのですが,大学の無線はWPA2-PEAPなのでこれではいけません.
そこで,設定方法を調べてみました.

結論

以下の設定を/etc/wpa_supplicant.confに追記します.

# for keiomobile2
network={
    ssid="keiomobile2"
    key_mgmt=WPA-EAP
    eap=PEAP
    identity="YOUR ID HERE"
    password="YOUR PASS HERE"
    phase2="auth=MSCHAPV2"
    priority=2
}
# for eduroam
network={
    ssid="eduroam"
    key_mgmt=WPA-EAP
    eap=PEAP
    identity="YOUR ID HERE"
    password="YOUR PASS HERE"
    phase2="auth=MSCHAPV2"
    priority=1
}

YOUR ID HEREYOUR PASS HEREはkeio.jpメールアドレスとkeiomobile2接続用のパスワードを入力します.
keiomobile2の方をeduroamよりも優先的に使ってほしいのでpriorityを高く設定しています.
これをテストするには,一旦wpa_supplicantサービスを止めたあと,wpa_supplicant -i <無線インターフェイス名> -c /etc/wpa_supplicant.confを実行してください.
eduroamの方をテストしたい場合はeduroamのpriorityを上げた後,もう一度上のコマンドを実行します.

大学の計算機でもChainerしたい!(環境構築編)

大学の計算機でもChainerしたい!

慶應では在校生向けに,計算機サーバを開放しています.使うのは簡単で,矢上のITCに紙ペラ一枚提出するだけです(矢上ITCには昔使われていたPDP-11が置いてあるので,ついでに見てくるのも良いでしょう.紙テープですよ!!).ks1.educ.cc.keio.ac.jpにsshすればアクセスできます.
参考:http://www.st.itc.keio.ac.jp/ja/com_keisan_st.html

スペックは
CPU: Intel Xeon E5-2698 v3 (16Core 2.3GHz) x 2
メモリ: 768 GB
と,見ているだけでワクワクしてきますね.コンパイラはIntel C++ Compilerが使えるみたいです.

折角計算資源があるのだから,最近流行りのニューラルネットワークでも試してみましょう.Chainerというpython製フレームワークは,次の一行でインストールすることができます.

pip install chainer

実行してみましょう.


[ua095595@ks1 ~]$ pip install chainer
pip: command not found

ふーむ


[ua095595@ks1 ~]$ python --version
Python 2.6.6

( ^ω^)・・・
2010年リリースのセキュリティフィックスも出てるバージョンですね・・・

悲しみに満ち溢れたのでホームディレクトリにpython環境を構築しましょう.ただし,以下の手順は計算機サーバではなくログインサーバで行います.なぜならば,計算機サーバにはgitも入ってないからです.

pyenvのインストール

初めに,pyenvをインストールします.pyenvは,pythonのバージョンを管理するためのツールです.
https://github.com/yyuu/pyenv#installationの通りにgit cloneして.bash_profileを編集しましょう.その後,exec $SHELLの代わりに一回sshセッションを切ってもう一度つなぎます.
すると,pyenvコマンドが使用できるようになるのでpyenv install 3.6.4でpythonの最新バージョン(2016/08/12当時)が手に入ります.

pyenv-virtualenvのインストール

次に,pyenv-virtualenvをインストールします.pyenv-virtualenvを使うことで,隔離された仮想なpython環境を利用することができます.
インストール手順はhttps://github.com/yyuu/pyenv-virtualenvに従いましょう.やはり,exec $SHELLは代わりにsshセッションをもう一回張りなおしましょう.
後は

pyenv virtualenv 3.6.4 envname(envnameは好きな名前)で仮想環境を作成し,
pyenv global envname

とすればその環境に移行できます.後はpip install chainerで環境構築の完了です!

大学の計算機でもChainerしたい!(実行編)に続く

慶應はLINE使ってない人が高々数百人なので○○

○○には好きな言葉を入れてお読みください.

先日三田祭が行われました.KCSも大盛況でした,ありがとうございます.

ところで,三田祭では塾生新聞という慶應の学生新聞の配布がありました.そこに,面白い記事があったので紹介します.学部生のSNSの利用実態をアンケートで調べています.

line

面白いですね.各種SNSの中でも一番使用率が高いLINEに注目してみれば,使っていない人はなんと0.3%しかいないようです!(グラフでは使用率99.4%ですが,本文中では99.7%で一貫しているのでそちらを採用しました.)アンケートのサンプル数は352人なので,LINEを使わないと答えた人は1人だけみたいですね.LINE使わない一派である筆者としては心細さで涙がちょちょぎれてしまいそうです.

では,慶應全体ではLINEを使用していない人は一体どれほどいるのでしょう.これは統計学を用いることで推定することができます.
以下では標本はランダムサンプリングされたことを仮定しています

今回のアンケート内での使ってない人の比率(これを標本比率といいます)から慶應の学部生全体で使っていない人の比率(これを母比率といいます)を推定することが目標です.LINEは使っているかいないかなので,LINEを使っていない人の人数は二項分布に従うことが期待できます.仮説検定の手法を用いて母比率の95%信頼区間を求めてみましょう.95%信頼区間とは,同じアンケートを何回も繰り返したときに,95%(以上)のアンケートで母比率が含まれていると考えられる区間のことです.

Rを使うことで,統計解析を簡単に行うことができます.ソフトウェアをインストールしなくても,Ideoneというオンライン処理系がRの実行環境も提供していますから,手軽に試すことができます.

やりました.結果から95%信頼区間をコピペします.

95 percent confidence interval:
0.000071923 0.015726126

ということで,慶應の学部生全体でLINEを使っていない人の比率はこの範囲に入っていそうだと言えます.学部生は全員で28855人なので,人数で言えば2人から454人あたりだろうと言えますね.

上にも書きましたが,この結果は標本がランダムサンプリングされている場合のみに正しいことに注意してください.上の画像にアンケートに答えた人の学部が載っていますが,明らかに標本のかたよりがあります.学部の人数差を考えても標本が0人の学部があるのは異常でしょう.標本を集めた方法はウェブアンケート以上は明記されていませんが,それこそSNSを使って友達づたいに協力を募ったとしたら,未使用率に負の影響があると予測できます.筆者は「それでも桁が変わるほどの差は無いだろう」と考えてこのタイトルを付けましたが,確率や場合の数は人間の直感をやすやすと超えていくので見当違いかもしれません.正確性を期すならばサンプリングが無作為になるよう工夫すべきです(あと記事の下の方で円グラフを使っているのもいただけない).

参考文献:

  1. 学生・生徒・児童数(慶應義塾大学)
  2. 検定と区間推定(三重大学・奥村研究室)