王様達のヴァイキング×SECCON スペシャルトークセッション 終わりました

王様達のヴァイキングの技術監修役としてSECCON 2016 東京大会でちょっとしゃべってました。

まさか花が飾られているとは……。

普通 IT 系の勉強会だと技術がわかっている人が多いので、その人たち向けの前提知識で話します。今回もそんな感じでいいかなって思っていたのですが、当日どうも女性が結構登録しているらしいと聞いて、これは考えを改めねばと思いました。IT 系の勉強会では残念ながら女性は多くても数人です(女子向けは除く)。ということは、これは技術がわかっている人と、おそらく技術にはそんなに詳しくはないがマンガに詳しい人たちが混ざっている……となるわけです。

両方の人たちを満足させなければならないので、技術的な話とそうでない小ネタ(主に設定)を半々ぐらいのバランスで挟むことにしました。両方の人に満足していただけたのかどうかはわかりませんが、当日最初らへんのしゃべりで反応があった数人にターゲットを絞って、まあ彼・彼女らが満足してればよかろうというスタンスでしゃべっていて、最後までわりと満足そうな顔をしていたので良かったのだと思います。

2017-01-29
告知 

王様達のヴァイキング×SECCON スペシャルトークセッション

王様達のヴァイキングの技術監修役として SECCON 2016 東京大会でちょっとしゃべることになりました。2017年1月28日13:00-14:00に東京電機大学だそうで。

SECCON 2016 決勝大会

セキュリティに関してはどう考えても来場者の方がレベルが高いと思いますが、漫画の屋台骨を支える役にはなんとか立っていると思っています。

2017-01-16
告知 

コミットメッセージの書き方

ここ5年ほど pre commit review があるような環境(主に chromium とか)で働いてきたので、コミットメッセージの書き方を自分なりにまとめておきます。

コミットメッセージで伝えたいことはパッチのコンテキストである

コミットメッセージはコードレビュー依頼であるという態度で記述します(受け売り)。そのため、コードレビュー者がパッチのコンテキストを理解できるように記述するべきです。なにをやっているのか分からないパッチを渡されるより、これはこういう理由でこういう変更をやっているパッチだということを伝えてからパッチを見たほうが、パッチの理解も速いでしょう。レビュー者にパッチのコンテキストを理解してもらえれば、コードレビュー速度が上がったり、コードのミスが見つけてもらいやすくなるという利点があります。

コミットメッセージの基本的なフォーマット

コミットメッセージには一般的に使われる基本的なフォーマットがあり、それにそって記述します。

1行目

1行目はパッチのタイトルのようなものです。なるべく50文字以内で、何をしたのかや改良点を簡潔に書きます。git log --oneline ではここだけ出て来るので、何をやっているのかを簡潔に書きます。ぼくは tig を使って履歴を良く見ていますが、tig でも1行目が git log --oneline と同じように出ます。

Implement something to improve stability
Add something
Remove something

50文字を超えない程度なら、to improve stability のように、理由を入れることもあります。

個人的には Fix xxx とか Modify xxx とかはほとんど書きません。自明なので。もっと内容のあることを書きたいからです。

最初は大文字、ピリオドはなし、ということになっているようですので、(最近は)それを守っています。

あとぼくは基本的に英語で書きますが、日本人だけしかいないなら日本語でもいいと思います。OSS にしたいのなら、日本人としては残念ですが英語が標準語になってしまっているので、英語で書いてください。まともな高卒ぐらいの英語力があれば伝わるものは書けると思います。

2行目

2行目は空行です。

ツール類に1行目をタイトルだと認識してもらうため、必ず空行です。

3行目以降

ここからが本番です。まず、このパッチが解こうとしている問題が何なのかを述べるようにします。つまり、このパッチがなかったら、どういうまずいことが起こるのかをレビュー者に伝えます。

このパッチに対応するチケットに問題が詳細に書いてあっても、「このパッチで」解こうとしている問題を述べるようにします。レビュー者がチケットを丁寧に読んでくれると思ってはいけません。チケットに書かれている問題は少し複雑で、いくつかのパッチを積み重ねて解決できるものもあるでしょう。レビュー者には、その問題のどこを解こうとしているのかは(コードを読まなければ)わかりません。コミットメッセージはコードを読む前に読んでもらうもの、という前提なので、コードを読まないと理解できないようなことは原則的には書きません。

If parameter `id` is not provided, this function could raise an
Exception, and it's not handled at all.
This update() could take O(N^2) time in the worst case. N is around 10
in the usual case. However, N could be 100 at the worst case, and this
often happens. It is too slow.

基本的に 72 文字以内で改行することが良いことになっています。

問題がレビュー者に伝わったら、それを「どのように (how)」解こうとしているのかを述べます。

So, the existence of `id` must be checked in the controller.
So, I implement O(N log N) algorithm like merge sort.

付加情報

最後に、チケットへのリンクやバグ番号を入れておきます。chromium では BUG=123 の形式で書くようになっていますが、github なら #123 のように書きます。

BUG=123

書かなくて良いもの

3行目以降には、べつに「何を (what)」やっているかは詳細には書かなくていいです。それは1行目に完結にまとめてください。1行目に書く必要が無いような情報なら、書かなくていいです。コードを読む助けにはなりません。パッチを見ればわかります。「何をやっているか」はパッチを読めばわかりますが、「なぜやっているか」はパッチを見てもわかりません。ですので、「なぜ」を書いてください。

Add class A. Rename A to B.

例外

何事にも例外はつきものです。typo の fix とかは1行メッセージで済ませることもあります。

2017-01-15

OpenBLASが速かった

ちょっと行列演算をしたかったので適当にコードを書き散らしていたのですが、速度が必要な演算であるために既成のライブラリを使うこととしました。今回は行列乗算ができれば良いのでBLASを生で使ってもそんなに問題ありません。BLASの中でも高速な実装であるOpenBLASを使い、適当に書いたコードと速度の比較をしてみます。

結論から言うと、OpenBLASの速さを舐めてました。まさか50倍の速度差がでるとは。

比較に用いたマシンは、MacBook Pro 2013 Late 13 inch (2.4 GHz Intel Core i5) です。

インストール

Macの場合はbrewで。LinuxでもOpenBLASのパッケージはたいていのディストリビューションにはあると思います。

$ brew install homebrew/science/openblas

Matrix クラス

1024 × 1024の大きさの実正方行列の掛け算をします。以下N = 1024とします。

const int N = 1024;

適当にMatrixクラスを定義します。精度はそこそこで良いので今回はfloatを使います。

class Matrix {
public:
    Matrix(int row_size, int col_size) :
        row_size_(row_size),
        col_size_(col_size),
        data_(new float[row_size * col_size]) {}

    float& operator()(int row, int col) { return data_[row * col_size_ + col]; }
    const float& operator()(int row, int col) const { return data_[row * col_size_ + col]; }

    float* data() { return data_.get(); }
    const float* data() const { return data_.get(); }

private:
    int row_size_;
    int col_size_;
    unique_ptr<float[]> data_;
};

operator()を定義していて、行列Ai行目j列にはA(i, j)でアクセスできるようにしておきます。

1024 × 1024 の行列 A, B を掛け算し、C = ABを計算するものとします。C++の変数としては小文字a, b, cを用いて次のように表すとします。

Matrix a(N, N);
Matrix b(N, N);
Matrix c(N, N);

ナイーブに掛け算をした場合

ナイーブに実装すると次のようになるでしょう。

for (int i = 0; i < N; ++i) {
    for (int j = 0; j < N; ++j) {
        float s = 0;
        for (int k = 0; k < N; ++k) {
            s += a(i, k) * b(k, j);
        }
        c(i, j) = s;
    }
}

この場合、b(k, j)の部分がメモリを飛び飛びにアクセスすることになるので速度はでません。clang -O3で試すと、おおよそ6〜7秒程度の時間がかかります。

乗算順序を変更

i, j, kの順序でアクセスするのではなく、i, k, jの順でアクセスするとメモリアクセスが飛び飛びにならないのでいくらか速度が稼げます。 ただしc(i, j)は0で初期化しておく必要があります。

for (int i = 0; i < N; ++i) {
    for (int k = 0; k < N; ++k) {
        for (int j = 0; j < N; ++j) {
            c(i, j) += a(i, k) * b(k, j);
        }
    }
}

これでおよそ1.1〜1.2秒ぐらいです。演算回数としては10243なので1G回の演算があるわけですが、2.4GHzのCPUで1.2秒となると、まあちょっと遅いですねという気持ちになります。

OpenBLAS の場合

関数cblas_sgemmを使います。cblas_sgemmは、行列A, B, Cとスカラーα, βαAB + βCを計算します。計算結果はCに格納されます。単に掛け算ABを計算したければ、βを0とすれば良いでしょう。

cblas_sgemmの引数はちょっと見ただけではわかりづらいです。

行列の要素はメモリ上では行ごとに並んでいるので、CblasRowMajorを指定する必要があります。FORTRANなどではメモリ上は列ごとにならべるのが普通ですがC/C++では行ごとにならべるのが普通です。また、cblas_sgemmでは転置してから行列を掛け算することもできます。今回は転置などせずに掛け算するので、CblasNoTransを指定します。

cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
    N, N, N,
    1, a.data(), N,
    b.data(), N,
    0, c.data(), N);

これの計算時間はわずか0.03秒でした。

ちなみにEigenでも同じような計算をしてみたのですが、こちらは0.11秒程度でした。それでも速いですね。

2016-01-17

ぷよぷよ AI

実機で動作するぷよぷよAIというものをしばらく有志で作っておりました。プロジェクトはgithub.com/puyoaiで公開しています。

これに関連するイベントを今までいくつか開いています。

また、先日行なわれたゲームプログラミングワークショップ 2015 (GPW-15)というワークショップで招待講演としてぷよぷよAI 人類打倒に向けてというタイトルで発表しました。スライドはリンク先の slideshare にアップロードしてあります。

2015-11-10

王様達のヴァイキングの監修について

なぜこんな仕事を?

簡単に言うと、深見先生に頼まれたから、です。

もともと、競技プログラミングのマンガを描こうという話が2005年ぐらいからありました。しかし、詳細を詰めるのが難しく、一度ネームもできたものの掲載には至りませんでした(注: 私が描いたのではありません)。このときの色々な縁で深見先生とは親しくなりました(注: 競技プログラミングマンガと深見先生は直接は関係ありません)。深見先生に王様達のヴァイキングの話がきたときに、せっかくだから我々とやりたいということで話が回ってきたものです。

ただ、話が回ってきたときには6話ぐらいは話が固まっていて、また、もともとの監修は sotarok と ozarn だけに回っていました。今がそうでないとは言いませんが、最初の頃はもっと起業ものっぽい雰囲気を醸し出していたでしょう?

深見先生が協力として入ることになった後に、旧知の仲である我々にも回ってきました。そのときはちょっと画像を提供するぐらいの話でしたし、そのつもりでいました。また、もっと監修は人がいました。具体的には、YUHAのメンバーと、Gokuriのメンバー、などです。だから、そんなに負担にならないと考えました。ただ、徐々に監修は人を減らしていきました。これはそこまで仕事がなくなったというのが大きいのと、あまり多人数いても意見だけが出てまとまらないためです。人が減ったとはいえ、あまり負担は変わっていません。マンガを読んで育ってきたので、一作関われるというのも良い機会だな、と思って楽しんで協力しています。

何をやっているのか?

技術的詳細を考えることです。

基本的に、話はさだやす先生が作ります。それを受けて、事件などを深見先生が作ります。しかし、そこには技術的な詳細は書かれていません。そこで、例えば「ハッキングをする」と書かれてある部分の技術的な詳細を考え、仮のセリフをいくつか挟む形にまで落とし、その詳細を平易な日本語でさだやす先生、深見先生、および担当の山内さんに説明します。なるべく、話も読者に分かりやすい展開になるように僕は心がけています。テクニカルには別の手段を使ったほうが良い技術でも、今後の展開および、マンガとしての要請のために別の技術を敢えて用いることもあります。これは詳しい人からはツッコまれますが、それは覚悟の上です。また、マンガの上では詳細はかけません。かいても難しすぎるだけだからです。

ハッキングの技術的難易度を調整するのは難しいのですが、私が見ている分に関しては、最低でも「理論的にはできる」話にしてあります。ほとんどの場合、それを実際にやったことがある人を聞いたことがあるものを採用しています。Windows のバイナリパッチを作った話なんかは、実際にそういう人がいるという話を聞いたので、じゃあいいだろうということで通してあります。また、過去に似たようなハッキング事例があれば、それを参考に技術的詳細を詰めたりします。大体5割ぐらいの技術的詳細は僕が決めていると思いますが、週に数時間程度の時間でだいたいなんとかなっています。

2014-08-14

Shinya Kawanaka / 川中 真耶 / a.k.a. MAYAH

English version? See LinkedIn / 英語版は LinkedIn へ。

書いたもの、作ったものは Publications をご覧ください。

その他の活動場所

スキル

プログラミング言語

2014年8月現在。

  • C/C++
  • Java
  • OCaml
  • Scala
  • ECMAScript/JavaScript
  • Ruby
  • go
  • Python
  • scheme
  • Objective C

他にも、Haskell, D, SML, Prolog, Concurrent Clean, ……などを書いてきましたが、5,000行にみたないものは省略します。

なんだかんだ言ってC++を一番書いています。最も好きなのはOCamlです。ほかは Java, Scalaあたりを一番使ってきたと思います。 仕事ではC++とかgoを書いていることが多いです。JavaScriptも必要に応じて結構書きます。

プログラミングコンテスト

ICPCあたりに出場することに、過去お熱でした。我々が現役の頃は、今と違って国内のICPCはやや平和な感じでしたが、世界には通用しませんでした。我々の下の代あたりから、半分がICPC OB/OG会のせいで半分はIOIのせいだと思うのですが、急激に強くなっていった印象があります。

ICFPプログラミングコンテストも過去2年に一回ぐらい色々出場していますが、わりと成績は忘れています。これは、どちらかというとお祭りです。

  • ACM ICFP 2014: 成績はいまいちでした。40位ぐらい。
  • ACM ICFP 2013: 8位。色々まだできることはありそうでしたが、全体としてはそう悪くはなかったです。
  • ACM ICFP 2011: この年は出題側で、対戦サーバーのほとんどを3日ぐらいで書いて運用していました。大変だった……。
  • ACM ICFP 2010: 成績忘れましたが、良くはなかったです。
  • ACM ICFP 2007: 83rd place (3 member): この年は最もひどかった。
  • ACM ICFP 2004: 46 位。一人でやってて疲れた記憶しかありません。
  • ACM ICFP 2003: 9位。学科の友達何人かと参加。

その他

2014-08-12

書いたもの / 作ったもの

本 / 電子書籍

同人活動の一環として発行した本は、同人サークルYUHAの発行物をごらんください。 そのほか、SIG2Dにも寄稿しています。

ソフトウェア

作ったもののうち、ある程度面白そうなものを。

監修

Papers

  • Shinya Kawanaka, Yevgen Borodin, Jeffrey P. Bigham, Daren Lunn, Takagi Hironobu, Chieko Asakawa: Accessibility commons: a metadata infrastructure for web accessibility. In Proceedings of the 10th international ACM SIGACCESS Conference on Computers and Accessibility (Halifax, Nova Scotia, Canada, October 13 - 15, 2008). Assets ‘08. ACM, New York, NY, 153-160
  • Hironobu Takagi, Shinya Kawanaka, Masatomo Kobayashi, Takashi Itoh, Chieko Asakawa: Social accessibility: achieving accessibility through collaborative metadata authoring. In Proceedings of the 10th international ACM SIGACCESS Conference on Computers and Accessibility (Halifax, Nova Scotia, Canada, October 13 - 15, 2008). Assets ‘08. ACM, New York, NY, 193-200
  • Shinya Kawanaka, CSS 2007: JavaScript Injection Prevention by Anomaly Detection (Good Paper Award)
  • Shinya Kawanaka, Haruo Hosoya: biXid: a bidirectional transformation language for XML. ICFP 2006: 201-214. (This is also a master thesis.)
  • Shinya Kawanaka, Region Inference for dynamically typed language (Bachelor thesis)

Slides (Japanese)

slideshare上にもスライドがいくらかあります。

Articles (Japanese)

  • ASCII .technologies (2010-06) クラウドのいる場所. 8 pages.
  • ASCII .technologies (2010-10) Cassandra からのぞく NoSQL の世界 8pages
  • ASCII .technologies (2010-11) Cassandra からのぞく NoSQL の世界 8pages
  • ASCII .technologies (2010-12) Cassandra からのぞく NoSQL の世界 8pages
  • ASCII .technologies (2011-01) Cassandra からのぞく NoSQL の世界 8pages
  • ASCII .technologies (2011-02) Cassandra からのぞく NoSQL の世界 8pages
  • ASCII .technologies (2011-03) Cassandra からのぞく NoSQL の世界 8pages
  • ASCII .technologies (2011-04) Cassandra からのぞく NoSQL の世界 8pages
2014-08-11

ソースコードのバランスシート

バランスシートには、資産、負債、純資産の項目があり、資産=負債+純資産が常に成り立ちます。さて、いまここにあるソースコードを資産だと思いましょう。すると、それは負債と純資産からなっている、ということになります。

では、ソースコードにおいて、負債や純資産とは何でしょう。

負債は後で返さなければならないものです。借りっぱなしにしておくと、利子が発生します。ソースコード上でこれに対応するのは、汚いコード、ゴミコード、やっつけ仕事のコードです。これは、とりあえずソフトウェアを動かすには必要なことがあります。借金のおかげで急場をしのげて助かった、そういう経験がある人もいるでしょう。ですが、負債なので、あとで返済しなければなりません。世の中には、技術的負債という単語があります。まさにそういうことです。

ここで少し注目したいのは、コードにも利率が存在することです。本当のゴミコードは、ヤミ金の如く利率が高く、真っ先に返済しなければなりません。後で問題になるかもしれないけど、まあしばらくは問題が起きないだろうというコードもあって、こちらは利率が低いので後回しでも構いませんし、実際に後回しにされます。

純資産とは何でしょう。これは、問題が起きにくいコード、きれいなコード、よくテストされたコード、などとなります。ここで面白いのは、純資産にも利子が存在することです。純資産が溜まっていくと、その部分のコードは問題がおきにくく、それに依存したコードの開発が加速します。これは非常に良いことで、こういうコードをたくさん積み重ねていくことで、企業にとっての価値になるでしょう。

純資産の方には、技術的負債のような単語を見つけることができませんでした。何か良い言葉、ありますか?

2011-02-09

OCaml 基礎最速マスター

OCamlとは

OCaml は Haskell とは違って純粋でない関数型言語です。ML (Meta Language) という言語ファミリーの方言の一つで、フランスの INRIA という研究所で開発されています。速度を稼ぐために命令型のように書こうと思えば書けるし、遅延評価もデフォルトではしません。その分、実用的なアプリケーションが書きやすくなっています。

他の言語をある程度知っている人は、これを読めば OCaml のとりあえずの基礎をマスターして OCaml を書くことができるようになります。他の関数型言語の知識は仮定していませんが、C/C++ ぐらいの知識があれば読めると思います。元の Perl 基礎文法最速マスターではリファレンスぽい作りですが、チュートリアルぽくなってしまいました。

なお、読んでいると分かりますが、色々とめんどくさいことが多いように感じます。しかし、これをちゃんと書くと、コンパイルに通るだけでかなりの数のバグがつぶれてくれているという恩恵を受けられます。我慢して使うといいことがあります。はじめは我慢してみてください。

この文章には不足やバグがいっぱいあると思いますので是非指摘してください。

他の最速マスター系は はてな的プログラミング言語人気ランキング あたりを見てください。

基礎

OCaml はコンパイルして実行することも対話型インタプリタで実行することもできますが、ここでは対話型インタプリタで実行します。対話型インタプリタは ocaml コマンドで起動します。

$ ocaml
        Objective Caml version 3.11.1

#

とりあえず、適当に値や式を打ち込み、;; を入力するとそのまま評価されて出力されます。

# 1;;
- : int = 1
# "some";;
- : string = "some"
# 1.0;;
- : float = 1.
# 'a';;
- : char = 'a'

ごらんの通り、型と値が表示されます。1.0 は float 型になっていますが、OCaml の float は C の double と同じです。C の float に相当する型は、標準にはありません。

print 系で表示をするには、print_int などのように型を付けるか、Printf.printf を使います。

# print_int 1;;
1- : unit = ()
# print_string "some";;
some- : unit = ()
# print_float 1.0;;
1.- : unit = ()
# print_char 'a';;
a- : unit = ()
# Printf.printf "%d%s%f%c" 1 "some" 1.0 'a';;
1some1.000000a- : unit = ()

ここで、1- : unit = () の様に表示されているのは、1print_int によって表示された部分で、- : unit = () は OCaml のインタプリタによって表示された部分です。OCaml では全ての式は値を持ちますが、特に意味のない値として、() という値が定義されています。これは unit という型を持ちますが、void みたいなものだと思ってしばらくはかまいません。

なお、コメントは、(* コメント *) のように (* *) でくくります。コメントはネストができるので、(* (* some *) *) も正当なコメントです。

変数

OCaml の変数は普通の言語の変数とはちょっと違って、「値に名前を付けたもの」です。普通の言語の変数は「値を入れる箱」の名前であり、その中身をいくらでも変更ができますが、OCaml は「値に名前を付けたもの」なので、中身を変更することはできません。ただ、同じ名前を再び付けてしまって実質書き換えたようにみせることはできます。C でいうところの const が付いていると思ってもらえれば、しばらくは OK です。

OCaml の変数は let 式で定義します。let 式は let 変数名 = 式1 in 式2 のように使います。「式1」の値の名前を「変数名」で名付け、「式2」中で参照できるようにします。

# let x = 1 in x;;
- : int = 1
# let x = 1 in
  let y = x in
  y;;
- : int = 1

なお、トップレベル (要するに、他の式の中ではない)で let 式を書く場合は、in 以下を省略して、グローバル変数的な扱いにすることができます。

# let x = 1;;
val x : int = 1
# let y = x;;
val y : int = 1

この場合、式の値を出力した部分に変数名が付いていることが確認できます。(val x の部分)

四則演算

四則演算は +, -, *, / が普通に使えます。int に限っては。あまりの演算は mod です。

# 1 + 1;;
- : int = 2
# 3 - 2;;
- : int = 1
# 2 * 4;;
- : int = 8
# 7 / 2;;
- : int = 3
# 6 mod 3;;
- : int = 0

float 型の値では、各演算子の後に . を付けなければなりません。めんどいです。すいませんすいません。

# 1.0 +. 1.0;;
- : float = 2.
# 3.0 -. 1.0;;
- : float = 2.

しかも float から int を引こうとすると、自分で intfloat に変換しなければなりません。これには float 関数が使えます。本当にめんどくさいですね。すいませんすいません。これには訳があるのです。それは次の章で。

# 3.0 -. (float 1);;
- : float = 2.

また、変数の章で説明しましたが、変数は一度値を入れると変更できないので、+= に相当するような演算子はありません。なくはないのですが、邪道なので。

ちょっとだけ型の話

どうして ++. が分かれているのかというと、OCaml が型に対して非常に厳密だからです。+intint の足し算と定義され、また+.floatfloat の足し算と定義されています。int と float の和というような定義の足し算は自分で作らない限りありません。OCaml は(一部の例外を除いて)勝手に型変換をしないので、intfloat にするのは自分で変換する必要があります。

いくつかのデータ構造

組み込みで単方向リスト、タプル、配列があります。レコードもありますがしばらくは使わなくてまあなんとかなるでしょう。単方向リストやタプルは非常に良く使うので重要です。

(* 単方向リストは、要素を [] で囲んで ; で区切って書きます。 *)
# [1; 2; 3];;
- : int list = [1; 2; 3]
(* 先頭への追加は :: を使います *)
# 1 :: 2 :: 3 :: [];;
- : int list = [1; 2; 3]
(* @ でリストを連結できます *)
# [1; 2] @ [3];;
- : int list = [1; 2; 3]
(* リストには同じ型のものしか入りません *)
# [1; 1.0];;
Error: This expression has type float but an expression was expected of type
         int (* int が予測されているところに float が来ました *)

List.hd で 先頭要素、List.tl で先頭要素を除いたリストをとれます。

# List.hd [1; 2; 3];;
- : int = 1
# List.tl [1; 2; 3];;
- : int list = [2; 3]
(* タプルは、コンマで区切って書きます。一応括弧を付けてください *)
# (1, 3, "some");;
- : int * int * string = (1, 3, "some")
(* タプルは同じ型じゃなくてもかまいません。*)

2つ組の場合は、fst, snd でそれぞれ1つめの要素、2つめの要素をとれます。3つ組み以上の場合は fst, snd は使えません。この辺は後のパターンマッチの章で出てきます。

# fst (1, 2);;
- : int = 1
# snd (1, 2);;
- : int = 2
(* 配列は [| |] 中に要素を入れます。これも同じ型じゃないと入りません。*)
# let x = [|1; 2; 3|];;
val x : int array = [|1; 2; 3|]
(* 要素へのアクセスは、x.(n) のようにします。なんか変な文法ですね。*)
# x.(0);;
- : int = 1
(* 配列の要素は実は更新可能です。配列そのものは更新できません。*)
# x.(0) <- 2;;
- : unit = ()
# x;;
- : int array = [|2; 2; 3|]
(* 配列の要素数は Array.length で取ります。*)
# Array.length x;;
- : int = 3

制御式

if は通常 if-then-else で書きます。

if 1 = 1 then 2 else 3

値の比較は = です。== だとポインタで比較になります。デフォルトでは = で OK。値の否定の比較は<> で、ポインタでの否定の比較は != です。紛らわしいですね。いや本当に。あとの<, >, <=, >= は一緒です。

また if-then-else も型に厳密で、if の後は bool 値のみです。また、thenelse の後は両方とも同じ型でなければなりません。なので、次のようなのはだめです。

(* if の後が bool じゃなくて int*)
# if 0 then 1 else 2;;
Error: This expression has type int but an expression was expected of type
         bool
(* then は int なのに else は float *)
# if true then 0 else 1.0;;
Error: This expression has type float but an expression was expected of type
         int

else 以降は省略できますが、省略すると else () があるとみなされます。()unit 型の値でした。つまり、thenunit しかとれなくなります。

if をネストするとき、C の { } 相当のものを入れないと、内側の ifelse がぶらさがってしまう問題があります。C の { } 相当のものは begin end です。

(* 何も表示しないつもりなのに 2 が表示されてしまった *)
# if true then
    if false then print_int 1
  else
    print_int 2
  ;;
2- : unit = ()

(* 今度は OK *)
# if true then begin
    if false then print_int 1
  end else
    print_int 2
  ;;
- : unit = ()

この後オリジナルでは文字列の操作などが続きますが、OCaml はそういうのはあまり得意でないのでばっさり省きます。必要なら String 系のモジュールのマニュアルを見てください。

なお、whilefor もあるのですが、邪道なのでここでは触れません。ではループどうするのか? という問いは後の章で説明されます。

関数

やっと関数型言語ぽい説明をするところまで来ました。

関数適用に関しては、実はすでに使っています。関数 引数 引数 ... というように、空白で区切って連続して書けば関数適用になります。

$ print_int 1;;
1- : unit = ()
# Printf.printf "%d%s%f%c" 1 "some" 1.0 'a';;
1some1.000000a- : unit = ()

OCaml は関数型言語なので、関数も値です。従って名前を付けて変数に入れることができます。関数は fun 引数 引数 ... -> 式 の様に定義します。関数の返値は最後に評価した式の値です。なお、return はありません。

# let f = fun x y -> x + y;;
val f : int -> int -> int = <fun>
# f 1 2;;
- : int = 3

毎回 fun を書くのはめんどくさいですね。そういう場合の略記法もあります。

# let f x y = x + y;;
val f : int -> int -> int = <fun>

ここで、関数の型に注目すると、fint -> int-> int という型になっています。これは、int を2つ引数に取って(最後の) int を返す関数、と実戦的には読みます。本当は違いますけどね! 知りたい人は「カリー化」という単語でググってみてください。

さらに! 関数の定義を良くみると、どこにも int と書いてないのに勝手に int を2つ取って int を返す関数になってますね? これは、+intint の足し算である、と決めたせいです。正しく足し算するためには + の左辺と右辺は int なので、xyint じゃなければなりません。また、int + intint なので、f 自体も int を返すはずです。というわけで、fint を2つとって int を返す関数でなければならないのです。このように、勝手に型を決めてくれる機能を、型推論と言います。これのおかげでバグが非常に減ります。ありがたやありがたや。たまにうざいけどね!

なお、関数は別に変数に束縛する (という単語は初めて出てきましたが、値に名前を付けるという意味です) 必要なく使えます。

# (fun x y -> x + y) 1 2;;
- : int = 3

また、関数の引数としても渡せます。

# let apply f x y = f x y;;
val apply : ('a -> 'b -> 'c) -> 'a -> 'b -> 'c = <fun>
# let add x y = x + y;;
val add : int -> int -> int = <fun>
# apply add 1 2;;
- : int = 3

おっと、'a とか 'b とか 'c とかいう型はなんでしょう。これは多相型というものです。

多相型と型推論

型推論が行われた際に、完全に型を決めきれないと多相型というものが現れます。 上の apply なんかがそうで、'a とか 'b とかいう型が出てきています。非常におおざっぱにかつ乱暴に言うと、OCaml の関数はデフォルトでもう C++ のテンプレート関数みたいなもんだと思ってください。

たとえば、

# let id x = x;;
val id : 'a -> 'a = <fun>

は、x がどういう型であれ自分自身を返します。

C++ でいうところの、

template<typename T>
T id(T x) { return x; }

みたいなもんです。

ループと再帰

関数中で自分自身を呼び出すこと(再帰呼出)で、ループをさせることができます。C 的には気持ち悪いですしスタックも使いそうですが、大丈夫。関数の最後で自分を呼び出す限りは、最適化が効いてループと同じコードになります。これは保証されています。なので、ループしようと思ったら関数書いて再帰します。なお、自分自分を呼び出すときは let じゃなくて let rec を使わなければなりません。そういうもんだと思ってください。letlet rec の違いは、式の中で自分自身を参照できるかどうかが違います。

# let rec loop i n =
    if i < n then begin print_int i; loop (i + 1) n end
    else ()
  ;;
val loop : int -> int -> unit = <fun>
# loop 0 10;;
0123456789- : unit = ()

言い忘れてましたが、; を使ってで式を2つ以上書けます。C と同じで begin-end で囲まないと ;if が終わるので注意。

データタイプとパターンマッチ

OCaml ではデータ型を定義することができます。データ型とは、C/C++ の enumunion を併せて、強力でかつ安全にしたものです。データ型は type で定義します。

# type v =
    | Int of int
    | Float of float
  ;;
type v = Int of int | Float of float

大文字から始まる IntFloat の部分をタグとか呼んでいます。タグは大文字で始まらなければなりません。一度このようにデータ型を作ると、

# Int 3;;
- : v = Int 3
# Float 3.0;;
- : v = Float 3.

のようににして値を作ることができます。型は両方とも v になっているのに注意。つまり同じ型なので、リストなどに入れられます。

# [Int 3; Float 3.0];;
- : v list = [Int 3; Float 3.]

データ型を一度作った後、中のデータを参照したいときはパターンマッチが使えます。これは非常に強力です。どうして他の言語にこれがないんでしょうか?

パターンマッチは match 式で下のように書きます。-> の前にパターンを書き、そこに変数名を書いておくと、パターンに合うように変数に値が束縛されます。

# match x with
    | Int v -> print_int v
    | Float v -> print_float v
  ;;
3- : unit = ()

パターンマッチはデータ型以外でも使えます。タプルとかリストとか。また、C の switch の代わりにもなります。

# let rec fstof3 x = match x with
    | (a, b, c) -> a
  ;;
val fstof3 : 'a * 'b * 'c -> 'a = <fun>
# let rec sum lst = match lst with
    | [] -> 0
    | x :: xs -> x + sum xs
  ;;
val sum : int list -> int = <fun>
# sum [1; 2; 3; 4; 5];;
- : int = 15
# let rec fib x = match x with
    | 0 -> 1
    | 1 -> 1
    | x -> fib (x - 1) + fib (x - 2)
  ;;
val fib : int -> int = <fun>
# fib 4;;
- : int = 5

さらに

OCaml はもっといろんな文法、機能がありますが、ここでは紹介しきれません。ocaml.jp には日本語の情報がもっとありますから是非そちらも見てくださいね!

2010-11-01