Rust で Generic な数値型を作る

ちまたで話題(?) の Rust 言語。昨年末くらいから「日本語情報が全然無い、先駆者になるチャンス!」とか思いながらのろのろしてたら、先日の0.1版発表で一気に知名度が上がってしまい(´・ω・`)としているgifnksmです。
0.1リリースでいろいろ変わりましたね。tag が enum になったり、alt で列挙型の値で分岐するときに末尾にどっと(.) をつけなくてもよくなったり。実装する予定の機能(Proposals · graydon/rust Wiki · GitHub) もまだまだたくさんあり、今後にますます期待です。
さてさて、そんなRust言語で、 Generic な整数型を作ってみました。
なぜ作ったかと言いますと、以下のようなコードを普通にコンパイルした場合、

fn add<T>(a: T, b: T) -> T { a + b }

以下のようなエラーでコンパイルが通らなくなってしまうからです。

main.rs:17:29: 17:34 error: binary operation + cannot be applied to type `'a`
main.rs:17 fn add<T>(a: T, b: T) -> T { a + b }

T型には + 演算子が定義されてねーよ!というエラーです。C++やDのTemplateとは違い、Rust は Generics によってポリモーフィズムを実現していますので、Generic 関数を呼び出した時点では無く、書いた時点で関数本体がコンパイルされ、コンパイルエラーになっているようです。
では、どうするか。Genericな数値を表すインターフェースを書いてやって、そのインターフェースに対する各数値型の実装を書いてやればよさそうです。関数を呼び出す側ではTではなくGenericな数値型を指定してやります。具体的には、以下の通り。

use std;
import std::io;

// Generic な数値型の定義
iface num<T> {
    fn val() -> T;
    fn zero() -> num<T>;
    fn add(++num<T>) -> num<T>;
    fn sub(++num<T>) -> num<T>;
}

// 実装 (uint)
impl of num<uint> for uint {
    fn val() -> uint { self }
    fn zero() -> num<uint> { 0u as (num<uint>) }
    fn add(a: num<uint>) -> num<uint> {
        (self + a.val()) as (num<uint>)
    }
    fn sub(a: num<uint>) -> num<uint> {
        (self - a.val()) as (num<uint>)
    }
}

// 実装 (int)
impl of num<int> for int {
    fn val() -> int { self }
    fn zero() -> num<int> { 0 as (num<int>) }
    fn add(a: num<int>) -> num<int> {
        (self + a.val()) as (num<int>)
    }
    fn sub(a: num<int>) -> num<int> {
        (self - a.val()) as (num<int>)
    }
}

// Generic な数値型を使った関数 (要素数が0の時は死ぬ)
fn sum<T>(vals: [num<T>]) -> num<T> {
    vec::foldl(vals[0].zero(), vals) { |s, t| s.add(t) }
}

fn main() {
    // [int] の和を求める
    std::io::println(#fmt("%d", sum(vec::map([1, 2, 3]) { |v| v as (num<int>) }).val()));
    // [uint] の和を求める
    std::io::println(#fmt("%u", sum(vec::map([1u, 2u, 3u]) { |v| v as (num<uint>) }).val()));
}

addで引数にとる数値型が自分と同じ型であることを保証するために、Tを型パラメータとしてnumに追加しています。また、num<T>型の"0"を取得する方法がわからなかったので、配列の0番目の要素に対して.zero()を呼び出すという格好悪い実装になっています(これは、ifaceが定数を持てるようになれば解決する、かも?)
上記のように、自分で実装したら不格好になりました。Language ProposalProposals for interfacesのOperator overloadingの節によれば、

iface num {
    fn +(self, self) -> self;
    fn -(self, self) -> self;
    /* etc */
}

のようなnumが組み込み型に対して定義されるようになるらしいので、ユーザー側でわざわざ不格好な定義をしなくてもよくなるかもしれません。
しかし、ユーザーが後付けで型に対する操作を追加できたり、限られたスコープでオープンクラス的に操作を追加できるあたり、Haskellの型クラスを思い出させます。後発言語なだけあって、インターフェースの仕様が洗練されてて良い感じだなー、と思います。
システムプログラミング言語、Rustの今後に期待ですね!!

2012/9/27 追記

最近の Rust では、コアライブラリにNumトレイトが標準で組み込まれています (以下は、2012/9/27 時点での incoming ブランチから持ってきた定義です)

trait Num {
    // FIXME: Trait composition. (#2616)
    pure fn add(other: &self) -> self;
    pure fn sub(other: &self) -> self;
    pure fn mul(other: &self) -> self;
    pure fn div(other: &self) -> self;
    pure fn modulo(other: &self) -> self;
    pure fn neg() -> self;

    pure fn to_int() -> int;
    static pure fn from_int(n: int) -> self;
}

こんな感じ実装&使用できます。

// bigint::add などは別途定義しておく
impl BigInt : Num {
  pure fn add(other: &BigInt) -> BigInt { bigint::add(&self, other) } 
  pure fn sub(other: &BigInt) -> BigInt { bigint::sub(&self, other) } 
  ...
  static pure fn from_int(n: int) -> BigInt { bigint::from_int(n) }
}

fn factorial(n: uint) {
  let mut prod: BigInt = from_int(1);
  for uint::range(2, n + 1) |i| {
    prod *= from_int(i); // 左辺の prod から型推論可能
  }
}

Num トレイトを実装した型については、演算子もオーバーロードされたものが使われます。Rustの演算子オーバーロードは core::opsAddトレイトなど、各演算子毎に定義されたオーバーロード用トレイトを実装するのが本来のやり方ですが、どうやらNumトレイトは特別扱いされているようです。

fn main() {
  let n: BigInt = from_int(1);
  let m = n + from_int(2);
  let k = n * m - from_int(10);
}

Titech Portal Auto Loginの更新とメンテナー募集のお知らせ

Titech Portal Auto Loginを更新しました。更新内容は,ソースコードの整理です。
作者修了につき,多分これが最後の更新になります。東工大ポータルの仕様が変わらない限り現在のスクリプトは動き続けると思いますが,将来的に正しく動き続けるかどうかは分かりません。
というわけで,スクリプトを更新してくれるメンテナーさん募集です。MITライセンスにしたので,黙ってフォークしてくれても全然構わないのですが,名乗り出て頂けると現在のユーザーさんに告知できるので何かとありがたいです。バグフィックスのみならず,新機能をどんどんつけちゃうぜーってのも歓迎です。JavaScript分かるぜーという東工大生さんは是非。

2010/2/22 02:28追記

id:eagletmtさんがGoogle ChromeのUserScriptに移植してくださいました! titech_portal_auto_login.user.js
あと,Firefox版もForkしやすいようにgistにアップロードしておきました。titech_portal_auto_login.user.js

2011/12/25 16:34 追記

@keisukefukuda さんがメンテナーに名乗り出てくださいました!ありがとうございます!!
今後東工大ポータルのログインページになにか変更があった場合、こちらのgistをご参照ください。
https://gist.github.com/1216571

ご紹介が遅くなって申し訳ありませんでした。。。

ニコニコ動画で watch ページにタグの履歴を表示する Greasemonkey スクリプト: NicoTag Tab を作った

インストールはこちら
NicoTag Tab for Greasemonkey

機能

  • データグリッド状にタグを表示します
  • グリッドの列毎にソートすることができます
  • 表示する要素をフィルタリングすることができます
    • XUL/Migemoがインストールされていると,ローマ字から日本語のタグをフィルタリングできます。

スクリーンショット

過去タグの一覧表示

存在期間の長い順でソート

ローマ字でフィルタリング


ここ最近はニコタグが503エラーを吐くことが多いのが残念です。運営者さん復帰しないかな…

ニコニコ動画でコメントを取得する方法のメモ

動画再生ページ上で動作するGreasemonkeyスクリプトから,動画についたコメントを取得する方法は主に2通りある。それぞれについてメモ。

新プレイヤーのAPIを使う方法

javascript: function hoge(data)alert(uneval(data)); void(document.getElementById("flvplayer").ext_getThreads('hoge'));

以下のような応答が得られる。

[{type:"main", id:0}, {type:"local", id:1}]

ここで得られたスレッドID?を使って,以下を呼び出す。

javascript: alert(JSON.stringify(document.getElementById("flvplayer").ext_getComments(1)));

以下のような応答が得られる。コメント番号降順で得られるっぽい。

[
  {"message": "mohrmohr", "resNo": 13, "vpos": 14970, "date": "Sun Nov 21 2010", "command": "184"},
  {"message": "ahogeahoge", "resNo": 12, "vpos": 7340, "date": "Sun Nov 21 2010", "command": "184"},
  {"message": "mohrmohr", "resNo": 11, "vpos": 3160, "date": "Sun Nov 21 2010", "command": "184"}
]

得られるコメントは,動画上で表示されているものと同じよう。10分超の動画なら1000件だし,短ければ得られるコメントも少なくなる。
メッセージサーバにアクセスしてXMLを取得する場合と比較したメリット・デメリットは以下の通り。

メリット
  • 通信が発生しない
    • 即座にコメントを取得できる
    • 複数のGreasemonkeyスクリプトからコメントを取得してもアクセス制限されない
  • JSのオブジェクトとして直接取得できる
    • パース処理が不要
デメリット
  • 動画に表示されているコメントしか取得できない
  • ユーザID,プレミアムか否か,コマンド等が取得できない
  • 投稿者コメントが取得できない?

コメント中のURLを抽出するなどの用途なら十分使い物になりそうですね。

メッセージサーバにアクセスする方法

昔からある方法。404 Not Foundを参考にすれば良さそう。
リクエストの<thread>要素の属性として,whenとwaybackkeyを与えれば過去ログも取得できる?

LaTeXのOTFパッケージ+新jsドキュメントクラスでredeffont

これまで

\usepackage[deluxe, expert]{otf}
\usepackage{redeffont}

を使っていて,見出しのフォントが太字にならないことに悩んでいた.今回,太字にならない原因と,その対策が分かったのでそれをまとめる.

そもそもredeffont.styが新ドキュメントクラスに対応していなかった

このことはredeffont.styのソースを読んでみれば当然な挙動であることが分かった.以下,redeffont.styの冒頭部分抜粋

\newif\if@asciiclasses \@asciiclassesfalse 
\newif\if@articleclass \@articleclassfalse
\newif\if@bookclass \@bookclassfalse
\@ifclassloaded{jarticle}{\@asciiclassestrue\@articleclasstrue}{}
\@ifclassloaded{jbook}{\@asciiclassestrue\@bookclasstrue}{}
\@ifclassloaded{jreport}{\@asciiclassestrue}{}
\@ifclassloaded{tarticle}{\@asciiclassestrue\@articleclasstrue}{}
\@ifclassloaded{tbook}{\@asciiclassestrue\@bookclasstrue}{}
\@ifclassloaded{treport}{\@asciiclassestrue}{}

\if@asciiclasses \else \endinput\fi

\endinputは,これ以上のファイルの読み込みを停止するという命令.すなわち,jarticleやjbookなどの,旧ドキュメントクラスの場合のみredeffontの定義が読み込まれることになる.見出しのフォントが置き換えられないのも当然だ.

redeffont.styが不要だった

redeffontが使えない,それでは自分で定義を書くしかない.そう思い,jsarticle.clsの\section定義部分を見てみたら以下のようになっていた.

\if@twocolumn
  \newcommand{\section}{%
    \@startsection{section}{1}{\z@}%
    {0.6\Cvs}{0.4\Cvs}%
    {\normalfont\large\headfont\raggedright}}
\else
  \newcommand{\section}{%
    \if@slide\clearpage\fi
    \@startsection{section}{1}{\z@}%
    {\Cvs \@plus.5\Cdp \@minus.2\Cdp}% 前アキ
    {.5\Cvs \@plus.3\Cdp}% 後アキ
    {\normalfont\Large\headfont\raggedright}}
\fi

なんと,\headfontというコマンドが定義されているではないか.定義は以下のようになっていた.

\newcommand{\headfont}{\gtfamily\sffamily}

なるほど.こいつを置き換えてやればよさそうだ."jsarticle redeffont"でググっても情報が出てこなかったわけだ.

結局どうしたか

今後はredeffontの代わりに,以下のように宣言してやることにする.

\usepackage[deluxe, expert]{otf}
\renewcommand{\headfont}{\gtfamily\sffamily\bfseries}

結論

ソースコード読んだら割となんとかなる.たとえTeXでも恐れずに読むとなんとかなるかも.

Mathematicaでカレントディレクトリのパッケージを読み込む

やり方がわかったのでメモ.分かってしまえばかなり単純.

SetDirectory[NotebookDirectory[]];
<<hogehogepackage.m

これだけ.".m"ファイルに記述した定義が読み込まれます.

今後の課題

名前空間について学ぶ.Needs["Hoge`"]とか指定した時のみ,呼び出し側の名前空間を汚すようにしたい.

ニコニコ動画で getflv を呼び出すときに便利なGM用ライブラリ

NicoFilter Tabを書くときに使った関数をライブラリ化しました。gistにアップロードしてあります。ニコニコ動画でgetFlvを呼び出す関数

概要

ニコニコ動画用のGMスクリプトを書くときに,http://flapi.nicovideo.jp/api/getflv/sm*****を呼び出す処理が必要になることがあります。このAPIへのアクセスを簡単にするライブラリです。APIの呼び出し回数を減らして,アクセス制限にひっかからないようにするのも目的の1つです。

使い方

GMスクリプトの冒頭で

// @require        https://gist.github.com/raw/670311/2a32978c5b58a477af62c7ae3e4123403c873f8a/nicovideo_getFlv.js

などと記述しておきます (指定するURLは,最新版のものにしておくことをオススメします。上記のgistのリンク先から入手できます)。そして,getflvの情報を取得したいところで

nicovideo_getFlv('sm9', function(obj, responseText) {
 ...
});

こんな感じで書きます。responseTextにはAPIにアクセスして得られる文字列が,objにはその文字列をパースした結果のオブジェクトが代入されます。objのフォーマットは以下のような感じ

{ "thread_id": "1173206704",
  "l": "111",
  "url": "http://smile-pcm31.nicovideo.jp/smile?v=8702.9279",
  "link": "http://www.smilevideo.jp/view/8702/43375",
  "ms": "http://msg.nicovideo.jp/7/api/",
  "user_id": <ユーザーID>,
  "is_premium": "1",
  "nickname": <ニックネーム>,
  "time": "1289361631",
  "done": "true",
  "feedrev": "9eb78",
  "ng_rv": "434",
  "ng_up": [
    ["はー","はあああああああああああああああああああああああああああああ"],
    ["どー","どおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"],
    ["らっちー","らっち〜☆ミ らっち〜☆ミ"],
    ["つー","つううううううううううううううううううううううううううううううううううううううううう"]
  ],
  "hms": "hiroba01.nicovideo.jp",
  "hmsp": "2530", 
  "hmst": "1000000006",
  "hmstk": "1289361691.cROP5uDxwti4Dfb-3rFLRXoGIow",
  "rpu": {
    "count": 1031240,
    "users": [
      <ユーザー名1>,
      ...,
      <ユーザー名n>
    ], 
    "extra":0 
  }
}

各値の意味についてはgetflvの戻り値についてまとめ(2010年8月版) - MineAPの(開発)日記を参照してください。