Don't Repeat Yourself

Don't Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of all kinds. -- wikipedia

【競プロノート】bit全探索を学ぶ

全探索にもいろいろ種類があるそうで、今日は bit 全探索という方法を勉強したのでそれについてまとめてみます。まとめないと忘れる。

解きたい問題

この記事に載っている部分和の問題を解きます。説明のために問題文を拝借します🙏🏻

qiita.com

n 個の正の整数 a[0],a[1],…,a[n−1] と正の整数 A が与えられる。これらの整数から何個かの整数を選んで総和が A になるようにすることが可能か判定せよ。可能ならば "YES" と出力し、不可能ならば "NO" と出力せよ。
【制約】 ・1≤n≤100 ・1≤a[i]≤1000 ・1≤A≤10000
【数値例】 1)  n=3  a=(7,5,3)  A=10  答え: YES (7 と 3 を選べばよいです)

2)  n = 2  a=(9,7)  A=6  答え: NO

解き方のアイディア

普通に解こうとすると、まず与えられた文字列を split して、「+」を入れられる位置を全部網羅して…といった解法になると思います。が、それは少しスマートではないように思えます。ここで出てくるのが、bit 全探索という解法です。ただし、bit 全探索は少々コストの大きい計算で、たとえば n = 100 くらいになると計算時間は膨大です。*1

bit 全探索は何をする方法?

今回の実装では、指定した数 n - 1 の部分集合をすべて探索できます (0-indexed です)。具体的には、n = 2 だった場合、{0, 1} についての部分集合を全探索します。つまり、({φ},) {0}, {1}, {0, 1} を全探索してリストアップしてくれるというすぐれものです。

ちなみに、集合 A のすべての部分集合からなる集合を A の冪集合と呼びます。集合 A = {0, 1} があった際の冪集合は、先ほどの結果を利用して P(A) = {φ, {0}, {1}, {0, 1}} と記述できます*2。bit 探索ではこの冪集合を求めていると考えてよいと思います。

通常の全探索であれば計算量は O(4n) になりますが、この bit 全探索を用いれば、計算量は O(3n) に落ちます。

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n = 2;

    for (int bit = 0; bit < (1 << n); bit++) {
        vector<int> S;
        for (int i = 0; i < n; i++) {
            if (bit & (1 << i)) {
                S.push_back(i);
            }
        }

        cout << bit << ": {";
        for (int i = 0; i < (int)S.size(); i++) {
            cout << S[i] << " ";
        }
        cout << "}" << endl;
    }
}

出力結果は、

$ ./bit_search_prac
0: {}
1: {0 }
2: {1 }
3: {0 1 }

と出てきます。また、n = 5 とすると、集合 A = {0, 1, 2, 3, 4} となります。したがってその冪集合 P(A) は、下記のように出力されます。

$ ./bit_search_prac
0: {}
1: {0 }
2: {1 }
3: {0 1 }
4: {2 }
5: {0 2 }
6: {1 2 }
7: {0 1 2 }
8: {3 }
9: {0 3 }
10: {1 3 }
11: {0 1 3 }
12: {2 3 }
13: {0 2 3 }
14: {1 2 3 }
15: {0 1 2 3 }
16: {4 }
17: {0 4 }
18: {1 4 }
19: {0 1 4 }
20: {2 4 }
21: {0 2 4 }
22: {1 2 4 }
23: {0 1 2 4 }
24: {3 4 }
25: {0 3 4 }
26: {1 3 4 }
27: {0 1 3 4 }
28: {2 3 4 }
29: {0 2 3 4 }
30: {1 2 3 4 }
31: {0 1 2 3 4 }

といったように、すべてのパターンを網羅して出してくれます。初めてみたときちょっと感動しました。1行1行の結果を配列にしてもたせておいて、すべての項を足し上げれば、部分和問題が解けそうな気がしてきますね🙂

実際に使ってみる

まず解いてみた

実装そのものは決して難しくはありません。2重ループを回すだけです。最初のループで部分集合の全探索をし、内側のループで bit の表す集合を求めていくだけです。

#include <iostream>
#include <vector>

using namespace std;

int n;
int a[25];
int A;

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    cin >> A;

    bool existence = false;
    for (int bit = 0; bit < (1 << n); bit++) {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            if (bit & (1 << i)) {
                sum += a[i];
            }
        }

        if (sum == A) {
            existence = true;
        }
    }

    if (existence) cout << "Yes" << endl; else cout << "No" << endl;
}

この結果、

$ ./total_sum
3
7 5 3
10
Yes
$ ./total_sum
2
9 7 
6
No

記事と同じような期待通りの結果が得られます。

しかし、中でイマイチどういった操作を行っているのか腑に落ちませんでした。そもそも今回のように bit 演算を利用するとなぜ、部分集合を出力できるのでしょうか?原理を知る旅に出ましょう。

原理がわからない!

わからない点を整理します。

  • 最初のループの条件がいきなり黒魔術: for (int bit = 0; bit < (1 << n); bit++) がわからない。具体的には、(1 << n) で何が得られているのか?
  • if (bit & (1 << i)): 条件分岐に使用しているので、たとえば真が存在するなら bit & (1 << i) が 1 になるタイミングがあるのだろうけど、これはどういう原理で起きているんだろう?

ここから Playground が便利な関係で Rust で説明します(笑)。

シフト演算

(1 << n) の正体はシフト演算です。

まずシフト演算の一般的な話ですが、2進数において、ビットの位置をずらす演算をシフト演算と呼びます。左にずらずと左シフト、右にずらすと右シフトです。ちなみにコンピュータは内部はすべて2進数で表現されているので、1ビット左にずらすと任意の数値の2倍を得られ、1ビット右にずらすと任意の数値の1/2倍を得られます。

次に 1 << n をすると何が得られるかという話ですが、これは 2n を得る計算をしています。1 から n ビット左にずらすと得られる値を取得しています。Rust のコードですが、実際に動かしてみてみましょう。

fn main() {
    let bit1 = 1 << 1;
    let bit2 = 1 << 2;
    let bit3 = 1 << 3;
    let bit4 = 1 << 4;
    let bit5 = 1 << 5;
    
    println!("{}", bit1);
    println!("{}", bit2);
    println!("{}", bit3);
    println!("{}", bit4);
    println!("{}", bit5);
}

この結果は、下記のとおりとなります。

2
4
8
16
32

21, 22, ..., 25 という結果が得られているとわかります。せっかくなのでバイナリでも取得してみましょう。わかりやすさのために1も標準出力します。

fn main() {
    let bit1 = 1 << 1;
    let bit2 = 1 << 2;
    let bit3 = 1 << 3;
    let bit4 = 1 << 4;
    let bit5 = 1 << 5;
    
    println!("{:#08b}", 1);
    println!("{:#08b}", bit1);
    println!("{:#08b}", bit2);
    println!("{:#08b}", bit3);
    println!("{:#08b}", bit4);
    println!("{:#08b}", bit5);
}

結果は、

0b000001
0b000010
0b000100
0b001000
0b010000
0b100000

となり、1 がシフトした箇所に立っていっている様子がわかります。美しい🥰

ビットフラグ判定の &

bit & (1 << i) の正体はビットフラグの判定です。

ビット演算には & 演算があります。論理積を取る演算です。それぞれのビットについて両方とも1の場合は1を、それ以外の場合は0を返す演算です。ビット演算における & は、特定のビットを取り出すときに使用します。

まず「論理積を取る」について理解します。次のコードをご覧ください。

fn main() {
    let bit = 5;
    println!("{:#08b}", bit);
    println!("{:#08b}", (1 << 2));
    println!("{:#08b}", bit & (1 << 2));
}

標準出力の結果は次のようになります。

0b000101
0b000100
0b000100

&論理積の取得であったと思い出すと、

   0b000101
*) 0b000100
------------

が行われています(0b は Rust の標準出力上の話で、bit を示す接頭辞なので計算上からはスルーできます)。この掛け算を、各桁ごとに計算すると、

   0b000101
*) 0b000100
------------
   0b000100

です。1 * 1 = 1, 0 * 0 = 0, 1 * 0 = 0 です。これがまず、& 演算子の正体です。

次に、bit & (1 << i) の意味を見ていきましょう。(1 << i) については、先ほどは1 を左に i分だけシフトさせると書きましたが、 (1 << i) は i ビット目に1を立てるという意味でもあります。たとえば、1 << 3 は、3ビット目に1を立てます。つまり、bit & (1 << i) は、ビット bit に i 番目のフラグが立っているかどうかを判定しています。たとえば、bit = 3 だったときに (1 << 0) との論理積を取ると、

fn main() {
    let bit = 3;
    println!("{:#08b}", bit);
    println!("{:#08b}", (1 << 0));
    println!("{:#08b}", bit & (1 << 0));
}
0b000011 // bit = 3
0b000001 // 1 << 0
0b000001 // bit & (1 << 0)

0b000001 は、10進数では 1 を示します。つまり、0番目のビットの論理積は10進数上は 1 になります。これを if 文に入れ込めば、そのまま真偽判定に利用できますね!bit 全探索ではこれを利用していたのでした。

困ったときの標準出力

先ほどの C++ のコードにもう一度戻りましょう。上記を踏まえた上で、結局どういう動作をしているのか改めて見直してみます。状態遷移を追いたくなったら、することはひとつですね。標準出力して確かめてみましょう。コードを次のように改変してみます。

#include <iostream>
#include <vector>

using namespace std;

int n;
int a[25];
int A;

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    cin >> A;

    bool existence = false;

    for (int bit = 0; bit < (1 << n); bit++) {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            if (bit & (1 << i)) {
+                cout << "i: " << i << ", bit: " << bit  << ", (1 << i): " << (1 << i) << endl;
                sum += a[i];
            }
        }

+        cout << "sum: " << sum << endl;

        if (sum == A) {
            existence = true;
        }
    }

    if (existence) cout << "Yes" << endl; else cout << "No" << endl;
}

まず、1つ目のループで謎だった for (int bit = 0; bit < (1 << n); bit++) についてですが、たとえば n = 3 を与えると、bit < 8 が出来上がります。

bit全探索の紹介時に、 n = 100 くらいにすると計算量が、という話を書きましたが 2100 = 1267650600228229401496703205376 の計算をするので相当大きなループになってしまいます。仮に n = 16 を入れたとしても、216 = 65536 なので、それなりのループ量になってきます。さらに、中でもう一度 n = 16 なり n = 100 のループを回すので、計算量が大変ですね。

楽ちんではあるけれど、そこまで効率のよくない探索の方法ですね。

次に謎だった if 文の条件分岐の動きを見てみましょう。今回は条件分岐の中に入った際に、i の値と bit の値と 1 << i をした結果を標準出力するようにしています。また、sum もついでに取得しています。動かしてみましょう。

$ ./total_sum
3
7 5 3
10
sum: 0 // bit = 0 の出力です
i: 0, bit: 1, (1 << i): 1
sum: 7
i: 1, bit: 2, (1 << i): 2
sum: 5
i: 0, bit: 3, (1 << i): 1
i: 1, bit: 3, (1 << i): 2
sum: 12
i: 2, bit: 4, (1 << i): 4
sum: 3
i: 0, bit: 5, (1 << i): 1
i: 2, bit: 5, (1 << i): 4
sum: 10
i: 1, bit: 6, (1 << i): 2
i: 2, bit: 6, (1 << i): 4
sum: 8
i: 0, bit: 7, (1 << i): 1
i: 1, bit: 7, (1 << i): 2
i: 2, bit: 7, (1 << i): 4
sum: 15
Yes

i の遷移を見ると、 n の部分集合 (今回は配列のインデックスに対応するので、{0, 1, 2} の部分集合) をきちんと取得できています。n = 3 だったとき、部分集合は {φ}, {0}, {1}, {2}, {0, 1}, {0, 2}, {1, 2}, {0, 1, 2} ですね。バッチリ取得できています。あとはこれと、もともとの入力の [7, 5, 3] という配列とを対応させて足し上げ、合計値が10であるかどうかを確かめれば大丈夫です。

ビット演算の左シフトが、1インクリメントすると1つ左のフラグを1に変えていくことを利用して、部分集合を上手に取得できてしまうアルゴリズムを今回は学びました。ビット演算すごい!

参考記事

*1:ちなみにそういうときは DP を使用するようです。もしかすると、DP をそもそも使う問題なので、慣れてしまえば bit 全探索である必要はないのかもしれません。

*2:P = Power で冪の意味。

今年読んだ技術書(2019年)

今年も多くの技術書を読みました。その中で印象に残ったものをご紹介したいと思います。今年は忙しくて休日しか時間がなかったので、読んで知識を体系的に整理できたと思った本の冊数は少ないですが…

Functional Programming

2019年の4月くらいに5年目がはじまるということでやりたいこととしてあげたうちのひとつに、「関数型プログラミングについて深堀りする」というものがありました。その文脈で何冊か読みました。

Debasish Ghosh の書いた『Functional and Reactive Domain Modeling』は、いわゆる DDD によるアプリケーション構築をどう関数型に仕上げていくかを紹介してくれるいい本でした。これまで、Reader や Kleisli、Free などは名前を知ってはいたものの、実際のアプリケーションでどのように使っていったらいいか、想像がついていませんでした。しかしこの本を読んだことによって、アプリケーションの中でそれらの道具がどう使われるか、コード込みでわかるようになりました。この本は、関数型プログラミングのエッセンスである「小さな汎用性の高い部品を上手に組み合わせながら、大きいアプリケーションを作り上げる」を学習する上で非常にいい構成を取っていると思います。社内では私がこの本を紹介したことがトリガーとなり、この本を使った読書会も開かれており、私もそこに参加しています。

Miran Lipovača の『すごい Haskell たのしく学ぼう!』も読みました。本腰を入れて Haskell をやったのは今年が初めてです。最近、型システムや関数型プログラミングの理論周りに関連する論文を読む機会が増えてきており、その中に Haskell のコードが登場してくるものの、読むのに毎回 Google していました。ということで、せっかくなので勉強してみよう!ということで勉強しました。現状のわたしの Haskell 力は、「読めるけど」「1からはスルスル書けない」です。見ながらは書けるんだけど。この本は関数型プログラミングに登場してくる道具たちを細かく解説してくれていて、関数型プログラミングの入門にもいい本だと思いました。

また、Haskell のような言語を扱い始めると登場してくる「モノイド」や「モナド」の数学的な意味が知りたくなって、野崎昭弘『なっとくする群・環・体』や Saunders MacLane の『圏論の基礎』も読んでみました。先日書いた記事の通りで、モノイドやモナドについての数学的な側面が、ほんの少しだけ分かるようになってきました。圏論の本はまだ全然読み進めている最中ですが、「自己関手の圏におけるモノイド対象」と言われてもなんとなくどういう意味合いがあるのかがわかるようになってきました。これらの本を学ぶ中で、数学という学問がそもそも何を議論していて、それがどうプログラミングという分野に応用されてきたかがなんとなくわかってくるのがとてもおもしろかったです。

全体的に、関数型プログラミングの勉強については、今年は実りの多い年だったと思います。ただまだまだ Haskell で行うような型レベルプログラミングについては慣れていない面が大きく、来年はそちらにチャレンジしてみたいなと思っています。

Kubernetes

今年は仕事で Kubernetes にはじめて取り組みました。私の部署はとても恵まれていて、Kubernetes のプロが多くいます。彼らにすべてを託してもよかったのですが、何もわからずに議論しても何の実りもないと思ったので、自分でも勉強することにしました。同僚の @amsy810 さんが書いた『Kubernetes 完全ガイド』を読みました。Kubernetes は、私のようなインフラ・ネットワーク苦手系アプリケーションエンジニアに武器を与えてくれるすばらしいエコシステムです。Kubernetes そのものはとても広大ですが、完全ガイドはその名の通り、完全にガイドしてくれます。この本を読みながらコツコツエクササイズして、家で遊びながら楽しく学ぶことができました。

Distributed Systems

今年は一冊決定版が出ました。Martin Kleppmann の『Designing Data-Intensive Applications』です。夏くらいに日本語版も出ていたんですね。分散システム(大量のデータ、大量のアクセスを何個かノードを分けてさばく、くらいの意味に捉え直すといいでしょうか)については、これまでその場しのぎをして何度か乗り切ってきたのですが、この本を読んではじめて体系的に学ぶことができました。この本を読んで思ったことは、「道具は先人たちがたくさん用意しているのだから、あとはそれらの特徴を学んで、適切に道具を使いこなしていこう」でした。また、高度な分散システムは、それ自身が何をしているかを把握するのがとても難しくなってきています。1つのノードで障害を起こすと、それがバタフライ・エフェクトのように広く波及してしまいます。こういった複雑な問題に立ち向かう道具もたくさん用意されており、それを学ぶという点でもすばらしいですし、またそもそもそういった道具を適切に使いこなせば未然に防ぐことのできる問題も多いでしょう。私のように決して分散システムのプロではなく、知識もなく、闇雲に複雑性と戦っていたエンジニアを巨人の肩に乗せてくれるすばらしい本でした。

また、Brendan Burns の『分散システムデザインパターン』も、今の時代には必読だと思いました。サイドカーなど、分散システムを構築する際にわりと前提として語られがちな用語をひとつひとつさらっていくことができます。私のように、アプリケーションエンジニアだけれどもシステムアーキテクトもつとめていて、けれどもコンテナ側の知識が若干怪しくて議論に参加できないという人におすすめできる本だと思います。

Designing Software

ソフトウェア周りをどうデザインすべきかについては、今年は一冊いい本があり、人にも多くお薦めしました。John Ousterhout による『A Philosophy of Software Design』です。邦訳はまだ出ていない感じですかね。洋書を読むことに抵抗のない方は、ぜひ読んでみてほしいです。私は新人さんにとりあえずお薦めしています。この本では、「なぜソフトウェアデザインをしっかり行わなければならないか?」という問いを立て、その答えのひとつとして、「複雑性への対処」をあげます。複雑性があるというのは、変更をした場合にそれが他の多くの場所に伝播してしまうような状態や、あるいはコードを読む上で認知負荷が高い状態をそうであると定義しています。私がハッとさせられたもののよく考えさせられたのは deep module と shallow module の章で、これらの違いに意識したことがなかったため、これまでのコードを思い返させるいい章だったと思いました。同時に実現性の難しさも感じましたが。

Computer Science

プログラミング言語の基礎的な理論を深めたいと思っているので、猪股 俊光、山田 敬三による『計算モデルとプログラミング』を読みました。この本は大学の講義ノートを本に起こした本のようですが、大学ではこのくらい濃密な授業が行われていて羨ましいです。計算モデルにはいくつか種類があり、それらの間の計算能力には差がないことを一旦説明したあと、チューリングマシンについてまず議論し、そこから抽象機械型計算、命令型計算、関数型計算として帰納的関数λ計算、最後にPrologのような論理型計算をていねいに紹介していきます。プログラミング言語のモデルにどのような種類のものがあるかを整理できたいい機会でした。まだまだマスターできているとは言えませんが、何度も振り返りつつ自分で使いこなせるようにやりきりたいと思っています。

コンピュータそのものやネットワークに対する理解も深めたくて、引き続き何冊か読みました。『CPUの創りかた』は言うまでもない名著だと思いますが、はじめて読みました。私は電子回路は中学生の知識で止まっていたのですが、本書は順を追って説明してくれて大変実りある読書になりました。CPU 周りにでてくる用語はこれまでさっぱり知らなかったのですが、この本を読んでだいぶ何をしている存在かわかるようになりました。また、『マスタリングTCP/IP』と『TCP技術入門』も読みました。今回会社のゼミでネットワーク周りを扱うことになったのですが、TCP/IP の仕組みをさっぱり知らず、これも「それではまずい」と思って読みました。「輻輳制御」を読めるようになったので、入門は突破したと思います(笑)。

林 高勲、川合 秀実による『作って理解するOS x86系コンピュータを動かす理論と実装』も読み進めています。前半の OS に関する座学の章を読むだけ、という読み方をしていますが、それだけでも十分実りある読書になっています。ここまで細かく踏み込んで解説してくれている本はなかなかないと思います。さすがに自分で1から作る時間はまだ確保できていませんが、やりたいことの優先度を整理していつか取り組みたいと思っています。

群、モノイド、半群

数学を勉強している最中に話が出てきたので、少しまとめておこうと思います。理解に誤りがあったら教えて下さい🙇‍♀️ なお、私はモノイドやモナドといった言葉は、その成立条件について Functional Programming in Scala などの本でさらっと理解しています。一方で数学的な意味として一体どのようなものがあるのかなあということが気になって調べたという背景です。

群とは

群とは、集合 G とそこで定義される2項演算 x△y を組み合わせた代数系 (G, △) が、次の3つの条件 (群の公理) を満たすものです。補足ですが、△にはたとえば「+」のような演算が入ります。

  1. 結合法則: x△(y△z) = (x△y)△z
  2. 単位元をもつこと: x△e = e△x = x となる e をもつこと
  3. 逆元をもつこと: x△y=y△x=e となる要素 y をもつこと

ちなみに、参照した本では直前に「演算が閉じていること」を書いてあったのですが、3つの条件さえ満たせばいいよ、と書いてありました。が、ネットで改めて調べてみると、「演算が閉じていること」も条件に含まれそうな気がします。なので書き直すと、下記のようになるかと思います。

  1. 集合 G に対して一意的な演算 (△) が成り立ち、かつその演算に関して集合 G は閉じている。
  2. (下記、そのような演算に対して) 結合法則: x△(y△z) = (x△y)△z
  3. 単位元をもつこと: x△e = e△x = x となる e をもつこと
  4. 逆元をもつこと: x△y=y△x=e となる要素 y をもつこと

モノイドとは

モノイドは実際のところ、「1. 演算が集合内に閉じていること」「2. 結合法則」と「3. 単位元をもつこと」を満たす存在でしたね。なので、群よりかは少し「弱い」存在です。

半群とは

半群は、英語では Semigroup というそうです。私は cats の中でこの単語を見たことがありました。

半群は、「1. 演算が集合内に閉じていること」「2. 結合法則」を満たす存在です。なので、やはり群よりは「弱い」存在で、かつモノイドよりも「弱い」存在です。

余談) マグマ

調べていたら、「1. 演算が集合内に閉じていること」を満たす存在にも名前があるようです。マグマ (Magma) というそうです。勉強になりました。ブルバキが導入した用語なんですね。私はブルバキは聞いたことがあるぞ。

さらに余談) 群は加算のことである…?

ネットの記事を読んでいると、群は「加算 (+) のことである」と定義する記事をたまに読みます。しかし、これは前提条件が抜けていると思います。こう書くときに乗算を含まないのは、乗算の逆元を整数 Z の限りでは定義できないからです。しかし、有理数 Q まで拡張すれば、乗算の逆元は定義可能になります。乗算の単位元 e は 1 ですが、a * 1/a = 1/a * a = 1となるためです。したがって、「群は加算のことである」というテクストは、半分正解で半分誤りの可能性があるため、「整数の範囲で」「群は加算のことである」と説明するのが正しいのかなと思いました。

参考

クリエイティブ担当者から見た Rust.Tokyo #rust_tokyo

f:id:yuk1tyd:20191029174606j:plain
Photo by: @katsumata_ryo

まずはお礼をさせてください。10月26日に Rust.Tokyo を開催しました。ブログ等での反響を見る限り、みなさんお楽しみいただけたようで、主催者としては何よりです。カンファレンスを初めてやるメンバーが多い中で、前日まで準備にいろいろ時間がかかり、当日至らない点も多いだろうなと思っての開催でした。みなさんの温かい応援ありがとうございました。

とくに、スポンサーさんとスピーカーさんがあんなにたくさん集まるとは思っていませんでしたし、参加者のチケットがこんなに早く売り切れてしまうとも思っていませんでした。また、あんなに積極的でプロフェッショナルなボランティアスタッフのみなさんが集まってくださるとも思いませんでした。

Rust のような、日本での採用事例がまだまだ少ない言語のカンファレンスを開催するとなった際の一番の懸念として想定されたのは、カンファレンスへの集客だったのですが、Rust というプログラミング言語そのものの魅力、世界中の Rust コミュニティの力、熱狂的な日本のファンのみなさんのおかげで、そういった懸念を一切感じさせないようなカンファレンスになりました。私たちは、ただそれらが集まる場をたまたま主催者という形で提供しただけであり、巨人の肩に存分に乗せてもらったなという思いが個人的には強いです。

「応募しようと思ったのにチケットが売り切れてしまっていた!」という声も多々頂いており、そこは認識しています。ごめんなさい。今年は「スモールスタート」がテーマでした。来年はもう少し大きな会場でやりたいですね、@dorayaki_kun さん😏

私はクリエイティブのデザイン全般を担当させていただきました。デザイナーがどういった思いを込めてクリエイティブを作ったかについては、あまり語られることがないように思うので、せっかくですし記事にさせてください。

私について

中学〜大学くらいまで、ずっとデザインすることが好きで、大学生時代には Web デザイナーとしてアルバイトをしたり、あるいは今で言うフリーランスでいくつか仕事をしたりしていました。大学を卒業してからデザインはしばらくまともにはやっていませんでした。なので、久々にやることになって、大丈夫かなと正直最初は思いました。という感じです。

今回 Rust.Tokyo では、クリエイティブのデザインと英語のツイートを (@chikoski さんと手分けしながら) やりました。

各制作物について

ロゴにこめた意図とか

以前ツイートしたことがありますが、ロゴは下記のような意図をこめて作っています。

  • 当然ですが、Rust のカンファレンスであるとわかりやすいもの。
  • サムライブルー。テクノロジーのカンファレンスなので、安直ですが。
  • 海外の人を当初から呼びたいと思っていたので、日本らしさ。
  • 日本のなかでも東京でやるので、「東京」とシンプルにわかるもの。
  • 多くの人、性別や年齢、職種をこえて受け入れられやすいもの。できるだけ中性的になるように。

これらから、

  • Rust のロゴのギアを一部改変。他国でもやっている例があったので。
  • 色は日本の伝統色の瑠璃色を採用。
  • スカイツリーもいいけど、やっぱり東京タワーで。
  • 最近オリンピックのクリエイティブやその他多数のクリエイティブに採用されていて、東京の街中でよく見かける DIN フォントを採用。街中でよく見る = 受け入れられやすいかなと。

といった形にしました。

だいたいコンセプトが決まったタイミングでロゴにして形にしました。ロゴはイベント当日も好評だったようで、作者冥利に尽きるです。みなさん、受け入れてくれてありがとうございました。

f:id:yuk1tyd:20191028231932p:plain
ロゴ。"Tokyo" はちょっと太くしてあります。

サイト制作

当初は英語用サイトを公開していましたが、これはその時期に海外からの招聘スピーカーを確保するのが急務だったためです。日本語のサイトはもう少し早く登場させるべきだったかなと思いました。が、英語のサイトを用意したおかげで、海外だけど日本法人のある会社スポンサーさんなどもついてくれて、正解だったかなと思います。

イラレでデザインを1から全部作りました。

f:id:yuk1tyd:20191028231803p:plain
当初起こしたデザイン

ロゴの周りをリボンにしたのはなんとなくの思いつきです。

裏側は、@dorayaki_kun さんのおかげで Next.js と Netlify によって作られています。とくに Netlify は初めて使いましたが感動しました。静的サイトを手軽に作るには最高のツールです。おすすめ。

またオフライン対応もしてくれたようです。これは、海外カンファレンスでサイトがオフライン対応をしていて、タイムテーブルが見やすかったから真似してみたと本人は言っていました。

個人的な反省として、強いて言うなら、モバイルデザインから先に作るべきでした。Web 用のデザインをそのままレスポンシブにしたので、最後まで表示崩れが気になったのと、そもそも UX があまりよくなかったかなと思いました。情報を伝達するという役割は果たせていたように思いますが、モバイル対応は甘かったです。モバイルデバイス向けのデザインは来年の課題です。勉強します。

宣伝用スライド

Rust LT 会で宣伝させてもらうために作成したスライドもデザインしました。

グッズの制作

トートバッグとパーカーをデザインしました。といっても、ロゴをポンと載せただけですが。トートバッグ、好評だったようで何よりです。

と同時に、定番のパーカーも制作しました。

スタッフさん、スピーカーさんにお配りしたパーカーは、紺色ベースにロゴを白としてもよかったのですが、ありきたりなデザインだし中性的ではないなと思ってやめました。白地に紺色。実際に届いたパーカーは青がちょっと薄かったですが、それはそれでかわいく仕上がってよかったです。

「SAUNA」と合いすぎ。最高。

胸にロゴを入れるか背中にロゴを入れるか迷って、おしゃれさの観点から胸に入れてみたんですが、当日のスタッフはジップを開いて着ていることが多くて、ロゴが見えなくてスタッフかわかりづらかったなと思いました。当日暑いとみんな開いて着るので、ロゴは背中に入れるのがよかったかなと思いました。もしくは、お金が許せば胸と背中の両方に入れましょう。

(幻の) カンファレンスバッジ

前日のバタバタで公開されることなく終了しましたが、カンファレンスバッジの用意も考えてはいました。来年はカンファレンスバッジをしっかり用意したいです。デザインさせてください。

用意したいもの: 入り口用の看板とか横断幕?とか

よくカンファレンスに行くと見る、入り口にデカデカとある布みたいな看板も、来年は置きたいですね。あと各部屋の案内とか、そういったものも統一感のある形で用意したいと思いました。

まとめ

  • 久々のクリエイティブデザイン楽しかったです。
  • それなりにコンセプトを考えてデザインしました。
  • 来年はデザイナーさん来てくれると嬉しいです。性別や国籍をこえた、より多様な人々が集まるカンファレンスにしていきたいです。興味のある方はぜひ。

ターミナルに色をつけられる crate 「ansi_term」

Rust を書いてる際にコンソールでいろいろ出力を出して遊びたいことがあると思います(?).コンソールの出力にふと色をつけたくなって,コンソール文字列の色づけって Rust ではどうやってつけることができるんだろう?と思って調べたらこういう crate がありました.

ansi_term https://crates.io/crates/ansi_term

そもそも UNIX 等でターミナル上の文字列に色をつける際,どのようにして実現しているのかを知らなかったのですが,ANSIエスケープシーケンスというものを用いて色付け等ができるのだそうです.たまに操作ミスって [A とかでてくるあいつですね.

en.wikipedia.org

この仕組を使ったアートまで存在するとか.表現力豊かなんですね.

en.wikipedia.org

さて話がそれてしまいましたが,仕事で使ってみたので知見を放流しておきたいと思います.便利クレート特集.

なおリポジトリはこちらです.

github.com

色づけしてみる

実際にエラーメッセージに色をつけてみます.

use ansi_term::Colour;

fn main() {
    eprintln!("{}", Colour::Red.paint("エラーが発生しましたよ"));
}

すると,エラーメッセージがちゃんと赤く色付けされました.

f:id:yuk1tyd:20190713121837p:plain

次に,ドキュメントを読むと一部だけ文字色を変えられるようなので変えてみましょう.

use ansi_term::Colour;

fn main() {
    println!("一部の色だけを変えてみる: {}", Colour::Yellow.paint("色変わってますか??"));
}

結果は

f:id:yuk1tyd:20190713115104p:plain

ToString を use しておけば,先に文字列を作ってそれを println! 内で使用するといった使い方ももちろんできます.

使用可能な色についてですが,標準で黒や赤,黄色などが用意されている上に,標準の256色を番号指定で利用可能な他,RGB 値で自分の好きな色を指定できます.enum の定義をちょっと見てみましょう.

pub enum Colour {

    /// Colour #0 (foreground code `30`, background code `40`).
    ///
    /// This is not necessarily the background colour, and using it as one may
    /// render the text hard to read on terminals with dark backgrounds.
    Black,

    /// Colour #1 (foreground code `31`, background code `41`).
    Red,

    /// Colour #2 (foreground code `32`, background code `42`).
    Green,

    /// Colour #3 (foreground code `33`, background code `43`).
    Yellow,

    /// Colour #4 (foreground code `34`, background code `44`).
    Blue,

    /// Colour #5 (foreground code `35`, background code `45`).
    Purple,

    /// Colour #6 (foreground code `36`, background code `46`).
    Cyan,

    /// Colour #7 (foreground code `37`, background code `47`).
    ///
    /// As above, this is not necessarily the foreground colour, and may be
    /// hard to read on terminals with light backgrounds.
    White,

    /// A colour number from 0 to 255, for use in 256-colour terminal
    /// environments.
    ///
    /// - Colours 0 to 7 are the `Black` to `White` variants respectively.
    ///   These colours can usually be changed in the terminal emulator.
    /// - Colours 8 to 15 are brighter versions of the eight colours above.
    ///   These can also usually be changed in the terminal emulator, or it
    ///   could be configured to use the original colours and show the text in
    ///   bold instead. It varies depending on the program.
    /// - Colours 16 to 231 contain several palettes of bright colours,
    ///   arranged in six squares measuring six by six each.
    /// - Colours 232 to 255 are shades of grey from black to white.
    ///
    /// It might make more sense to look at a [colour chart][cc].
    ///
    /// [cc]: https://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg
    Fixed(u8),

    /// A 24-bit RGB color, as specified by ISO-8613-3.
    RGB(u8, u8, u8),
}

文字列への装飾もできる

太字や下線もできます.

use ansi_term::{Colour, Style};

fn main() {
    println!(
        "{}そして,{}",
        Style::new().underline().paint("下線引けていますか?"),
        Style::new().bold().paint("太字になっていますか?")
    );
}

f:id:yuk1tyd:20190713115819p:plain

まとめ

  • コンソールの文字列に色をつけられるクレートがあるよ.
  • 一般的なコンソールの色付けは ANSI エスケープシーケンスという仕組みを使って実現されているよ.

logback で環境ごとにロギング設定を変えたい場合にやること

ググると Spring も込みの設定方法しか出てこなかったので,メモしておきます.言語は Scala,ビルドツールは sbt を使用しています.logback-classic を使用しているようです.

やろうとしていること

  • logback.xml 内で,開発環境/ステージング環境/本番環境それぞれでログの仕方を切り替えたい場合
  • かつ,環境情報は System.setProperty(key, value) あるいは環境変数で設定されている場合

ハマったポイント

  • ドキュメントどおりに if 文を書いてみる.
  • 環境変数システムプロパティを正しく設定しているのに,なぜか <if condition='p("key").contains("value")'> がうまく効いていない.
  • それゆえ,if 文の内容がいつまで経っても反映されない.

原因

  • ログ上に出ている警告を注意深く読むと,「janino」というライブラリがクラスパスにないと言われる (知らんがな).

解決策

  • janino というライブラリを Maven や sbt で依存関係に加えておく.

一応,このページ の if 文の箇所に,

Note that conditional processing requires the Janino library.

とは書いてありますね.Janino Library の設定方法まで丁寧に書いてあります

お役に立ちますと幸いでございます.ちなみにいろいろ試して,30分くらい溶かしました.エラーメッセージは丹念に読みましょう.

ちなみに

この構文で取得できる p("key") の参照先は,システムプロパティでもよいですし,環境変数でもよいです.

システムプロパティであれば,java jar xxx.jar -DENV=dev などと書けば (あるいは System#setProperty を用いる手もあります), p("ENV").contains("dev")dev という値かどうか判定できます..また,環境変数であれば,環境変数ENV=dev とセットしたあとに同様に p("ENV").contains("dev") で判定可能です.

『実践 Rust 入門』

思い返すと、Rust をはじめて知ったのは2年前でした。ある Googler が記事の中で Rust に対して惜しみない賛辞を送っていた記事でした。私はこの記事に大変共感し、感銘を受けました。Rust がやりたくなった。

当時の私は、金融業界で使われるリスク管理計算機の開発に携わる開発者でした。当時は Java で計算機の業務ロジックや金融工学計算のロジックを書いていました。その業務を日夜送る中で、複雑化した並列処理計算や並行処理計算にまつわる多くの障害に直面しました。それらは原因がよくわからないけれども、並列・並行処理起因であることだけはわかる障害でした。

並列処理・並行処理で苦しめられた経験がなかったとしたら、Rust という言語に興味をもたなかったかもしれません。当時そういった苦しみを感じていた私は大きく感銘を受けました。プログラミング言語の処理系の力でこれほど多くの問題を解決できるのか、と思いました。プログラミング言語ってすごいですね。それが私の Rust との出会いでした。

Rust は私のプログラマとしての実力を格段に伸ばしてくれたと思います。Rust をやる上では避けられないメモリ周りの知識。あるいは関数型プログラミング由来のパターンマッチングやコンビネーターで実装をつなげていくスタイル。型クラス、静的ディスパッチ。ゼロコスト抽象。多くのプログラミング言語のベストプラクティスをふんだんに取り込んだ言語だからこそ、多くの学びがいに満ちていました。Rust を学習し使いこなすに際し、多くの用語を調べ、概念を苦労して理解し、吸収し、成長させてもらったなという気持ちです。学習コストとはそういうものだと私は思います。いわゆる No Pain, No Gain です。

あれから2年経ちました。Rust は当時とは比べ物にならないほどに流行の兆しを見せ始めています。海外では、StackOverflow の愛され言語ランキングにおいて、ここ数年つねに1位を取り続けています。Reddit でも、Rust に関する記事を見ない日はありません。

日本では、日本語訳された資料や文献がとても増えています。私は当時、Rust の多くを英語で勉強しました。書籍は当然ありませんでした。ネット上には資料はあったけれど。今は、日本語の、それも日本人の書いた書籍が出る時代になりました。感慨深さを覚えます。

その矢先、とてもいい本が出ました。

実践Rust入門[言語仕様から開発手法まで]

実践Rust入門[言語仕様から開発手法まで]

κeen さんより新刊を頂戴し、『実践 Rust 入門』を一足お先に読ませていただきました。ありがとうございます。日本の Rust ユーザーにぴったりな本だと思います。現場で「ここがかゆい」と思うところによく手が伸びていると思いました。これは、普段から Rust を使用している現場の達人にしかなしえないわざでしょう*1。私はこの巨人の肩に積極的に乗っていきたいと思いました。

『プログラミングRust』では、どういった機能が Rust に用意されているのかについての説明に多くの紙面を使っていますが、本書ではどういった機能があるかに関する説明に加え、各機能をどういった場面で使用したらよいのかについても言及されていることが多いです。 たとえば、Box<T> 型について説明する際、本書では次のように記載されています。

Box ポインタは以下のような場面で使われます。

コンパイル時にデータサイズが決まらない型を扱うとき。たとえば再帰的なデータ構造を実現するにはBoxやRcのような値を所有するポインタが必要になる。

・大きなデータをコピーすることなく、その所有権を他者へ移動したいとき。

・トレイトオブジェクトを作成したいとき。

Rust をはじめられる方の中には、もともと C/C++ ではなく、 JavaPython といった処理系が優秀で細かいメモリ制御をほとんど必要としない言語を触っていた方もいらっしゃるかと思います。私もその一人でした。他言語出身者からすると、Rust には、主にメモリ周りの機能に「どう使ってよいのかいまいちイメージがつかないもの」があります。それらの疑問に、本書はよく答えてくれています。私も読みながらはじめて用例を知ったものがありました。

本書は「基礎編」と「実践編」にわかれています。とくに9章からはじまる実践編は、実際にアプリケーションを作る章なのですが、「パーサー」「パッケージ」「Web アプリケーション」「FFI」と続きます。FFI が載っている本は、私の知る限りではありません。私も FFI を使った経験はなく、本書のチュートリアルをぜひこなしてみたいと思いました。

これから Rust をはじめるユーザーや、すでに使っているものの理解を深めたいユーザーにはうってつけの一冊であること間違いなしです。κeen さんもご自身でブログに本に関する話を書いているのでそちらもご覧ください。また、詳細な目次。個人的には、仕事で Rust をはじめるとなったときに「とりあえずこれを読んでおけ!」と自信をもって言える一冊が出版されて、本当に嬉しく思います。

実践Rust入門[言語仕様から開発手法まで]

実践Rust入門[言語仕様から開発手法まで]

*1:私は Scala ユーザーなので、Scala 界隈のアナロジーを用いて本書を説明しておくと、『プログラミングRust』がコップ本だとしたら、本書は『実践 Scala 入門』にあたるといえるでしょう。