「関数型プログラミングって何?」日本語訳
この記事は、技術翻訳 Advent Calendar 2016 の15日目です(枠が空いてたので勝手にお邪魔してます)。前回(6日目)は、id:msyksphinz さんの「個人が趣味で技術書を翻訳するという意義について」でした。
今回ご紹介するのは、昨年末に公開された Kris Jenkins さん (@krisajenkins) の "What Is Functional Programming?" です。日本語訳の公開については著者から承諾済みです。また、London Functional Programmers meetup での同タイトルの講演動画が公開されています。
関数型プログラミングの考え方は、世間ではどうも小難しい話だと思われている節があります。その理由の一つに、議論の抽象度が(比較的)高いことが挙げられるでしょう。例えば、以前このブログで紹介した「なぜ関数プログラミングは重要か」も、関数型プログラミングの本質をコンパクトに抽出した優れた論文なのですが、いかんせん数学的な考え方に慣れていない方にはとっつきにくい面がありました。
しかし、この記事では、我々が日々の仕事をこなしていく上で、関数型プログラミングがいかに実践的な方法論であるかを平易かつ具体的に説いています。特に、数学は一切出てこないのでご安心ください。
(いちおう技術翻訳ネタにも触れておくと、この記事、読む分にはスルスル読めたので楽勝かなーと思ってたんだけどそんなことは無かった…。以前 ScalaMatsuri ブログでも検証した通り、Google 翻訳の精度が上がってるので併用したら少しは楽になるかと思ったけど、こういう口語的な言い回しを多用する文章にはまるで無力でした。まる。)
関数型プログラミングって何? (What Is Functional Programming?)
これは、関数型プログラミングの本質は何なのかってことについての僕の見解で、とにかく目先の仕事を片づけたいと思っている職業プログラマ (jobbing programmer) に向けて書いたものだ。
この記事で僕が伝えたいのは、君が書くあらゆる関数には二組の入力と二組の出力があるってことだ。
二つ? え、一つだけでしょ?
いいや、二つ。間違いなく二つだ。一つ目のペアについて例を見てみよう:
public int square(int x) { return x * x; } // 補足: これが何の言語かは重要じゃないけど、入力と出力の型を強調するために明示的に宣言する言語を選んだ。
これを見た君は、この関数の入力は int x
で出力は int
だと考えるんじゃないか。
それが入力と出力の一組目で、言うなれば従来の捉え方だ。では続いて、入力と出力の二組目の例を見てみよう:
public void processNext() { Message message = InboxQueue.popMessage(); if (message != null) { process(message); } }
この関数は、構文を見るかぎりは何も入力を取らず何も出力を返さないように見えるが、何かに対して明らかに依存しているし、明らかに何かをしている。実は、この関数には入力と出力の組が隠れている。その隠れた入力とは popMessage()
を呼び出す前の InboxQueue
の状態だ。そして、隠れた出力とは process
が引き起こしたあらゆる結果と、それに加えて、処理が終わった後の InboxQueue
の状態だ。
間違いなく、InboxQueue
の状態はこの関数の本物の入力だ。その値を知らなければ processNext
の挙動も分からない。そして、出力の方も本物だ。processNext
を呼び出した結果は、InboxQueue
の新たな状態を考慮しないと完全に理解することはできない。
このように、二番目のコード片には入力と出力が隠れている。それは何かを必要として、そして何かを引き起こすが、API を見ただけではそれが何なのかは決して推測できない。
この隠れた入出力にはちゃんとした名前があって、その名を「副作用」という。さまざまな種類の副作用があるけど、それらは全て同じコンセプトの下でまとめられる。「引数リストに含まれないけど、この関数の呼び出しに必要なものは? そして、戻り値に含まれないけど行うことは?」
(僕は実のところ、隠れた出力を表す「副作用 (side-effect)」という語だけでなく、隠れた入力を表す「副原因 (side-cause)」という語も必要だと思う。これ以降の記事では、ほとんどの場所では簡潔に「副作用」とだけ書くが、その場合は間違いなく副原因の話もしている。僕は、あらゆる隠れた入出力についての話をしている。)
副作用は複雑性の氷山だ
関数が副作用(と副原因)を持つとき、こう見立てることができる:
public boolean processMessage(Channel channel) {...}
…で、これを見た君は、これが何をしているのか理解したと思うかもしれないが、それは全くの誤りだ。関数の中身を見なければ、その関数に何が必要なのか、あるいはその関数が何を行うのかを知る方法はない。チャンネルからメッセージを取り出して処理する? たぶん。何かの条件を満たしたらチャンネルを閉じる? おそらく。どこかのデータベースのカウンタを更新するのかな? ひょっとすると。期待したログディレクトリのパスを見つけられなかったら爆発するの? かもしれない。
副作用は複雑性の氷山だ。関数のシグネチャを、そして名前を見たとき、君はそれが何物なのかを何となく分かった気になる。しかし、関数シグネチャの表層の下には、本当にあらゆるものが隠れている可能性がある。あらゆる要件、あらゆる変更、そうしたものが隠れている。実際に何が関わっているかは、関数の実装を見なければ知る由もない。API の表層の下には、さらなる複雑性の膨大な塊があるかもしれない。それを把握する方法は三つしかない: 関数の定義に飛び込むか、複雑性を表層に持ってくるか、無視を決め込んでうまくいくことを祈るか。そして、無視すると、たいてい結局はタイタニック号と同じ過ちを犯すことになる。
これってカプセル化の話じゃないの?
違うよ。
カプセル化とは、実装の詳細を隠蔽することだ。呼び出し元がコードの内部のことを心配しなくても済むように隠そうって話だ。これは変わらず良い方針だけど、この記事で話してることじゃない。
副作用に注目するのは「実装の詳細を隠蔽したい」からではなく、コードとその外側の世界との関係を隠蔽したいからだ。副原因を伴う関数には、その関数が外部のどんな要素に依存しているかについて文書化されていない仮定がある。副作用を伴う関数には、その関数が外部のどんな要素を変化させるかについて文書化されていない仮定がある。
副作用は悪なのか?
いや、それがプログラムの元々の作者が期待した通りに動作するならおそらく大丈夫だ。ただ、副作用の難しいところは、僕らは元のプログラマが暗黙に期待していたことが正しいと、そしていくら時間が経っても変わることなく正しいと信じる必要があるってことだ。
僕らは、この関数を書いた際に期待していた通りに世界の状態を設定しただろうか? さもなくば、世界がどこかで変更された? それはおそらく、一見して繋がりのないコード片が変更されたせいだ。そうでなければ、そのソフトウェアを新しい環境にインストールしたからだ。世界の状態について隠された仮定があるとき、僕らは、世界の状態と、その関数が十分に動作する状態とが似ていると暗に期待していることになる。
このコードはテストできるだろうか? 単独では無理だ。回路基板と違って、僕らは入力にそのままプラグを差し込んだり出力を確認したりできない。僕らはコードをこじ開け、その隠れた原因や作用を把握し、それが存在するはずの世界をシミュレートする必要がある。僕は、テスト駆動開発に取り組んでいる人々が、テストをブラックボックスでやるべきかホワイトボックスでやるべきかについて堂々巡りしているのを見てきた。答えはブラックボックスでやるべきで、実装の詳細を無視できるはずだけど、副作用を許してしまえばそれはできない。副作用はブラックボックステストへの扉を閉ざしてしまう。なぜなら、箱をこじ開けて中身を調べなければ入出力へ到達できなくなるからだ。
これは、デバッグにおいてさらに問題になる。関数が副作用(や副原因)を許さなければ、その関数が正しいかどうか理解するには、単にいくつか入力を与えて出力を確認するだけでいい。だが、副作用を伴う関数だったら? システムの他の部分に対する影響を際限なく考慮しなきゃいけなくなる。関数が何かに対して依存したり影響を引き起こしたりするのを許容すると、あらゆる場所にバグが存在するようになる。
副作用は常に表層に移せる
僕らは、この複雑性に対して何かできるだろうか? うん、始めるのは実のところかなり簡単だ: 関数が何か入力を持つなら、そう言おう。何かを出力として返すなら、そう宣言しよう。それだけだ。
例を試してみよう。この関数には入力が隠れている。素早く見つけられたらボーナスポイントだ:
/** * 訳注: 指定したチャンネルで現在放映しているテレビ番組を返す関数。 */ public Program getCurrentProgram(TVGuide guide, int channel) { Schedule schedule = guide.getSchedule(channel); Program current = schedule.programAt(new Date()); return current; }
この関数には現在時刻 (new Date()
) という入力が隠れている。この複雑性を表層に移すには、こういう追加の入力があるってことをシグネチャの中で正直に表すだけでいい:
/** * 訳注: 第三引数に when を追加して programAt(Date) に与えている。 */ public Program getProgramAt(TVGuide guide, int channel, Date when) { Schedule schedule = guide.getSchedule(channel); Program program = schedule.programAt(when); return program; }
これで、この関数は入力(や出力)を隠し持たなくなった。
この新バージョンの良い点と悪い点を見てみよう:
悪い点
より複雑になったように見える。関数の引数が二つから三つに増えたし。
良い点
複雑にはなっていない。依存関係を隠してもシンプルにはならないし、それを正直に表したらより複雑になるってこともない。
テストははるかに簡単になっている。一日の異なる時刻、時計の変化、うるう年といったすべてのテストがそのまま書ける。なぜなら、好きな時刻を渡せるからだ。僕は、最初のバージョンのような本番コードをテストするために、あらゆる種類の巧妙なトリックで現在のシステムクロックを偽装するのを見てきた。これをパラメータ化するだけで、そんな苦労をしなくて済むんだ!
推論もより簡単になっている。いまや、関数は単に入力と出力の関係を記述しているだけだ。入力が分かれば出力がどうなっているべきか分かるし、そして結果についてあらゆることが分かる。これはすごいことだ。このコードは単独で検証できる。入力と出力の関係さえテストすれば関数全体をテストしたことになる。
(そして余談だけど、便利なのはこれだけじゃない。「一時間後に始まる番組」を返すようなコードがタダで手に入る。)
「純粋関数」って何?
ドラムロールをどうぞ。
これで隠れた入力と出力について把握したので、ついに「職業プログラマ向けの純粋関数の定義」を説明できる:
関数が〈純粋〉であるとは、全ての入力が入力として包み隠さず宣言されていて、同様に出力が出力として宣言されていることだ。
逆に、入力や出力を隠し持っている関数は〈純粋〉ではない。非純粋な関数にとっては、僕らが「関数が提供する契約」だと思っているものは全体のうちの半分に過ぎない。複雑性の氷山が迫っている。純粋でないコードは決して「単独」では使えないし、単独でテストすることもできない。テストやデバッグをしたいときはいつも、そのコードが依存する他のコードを追跡しなければならなくなる。
「関数型プログラミング」って何?
純粋関数と非純粋関数について把握したので、これで「職業プログラマ向けの関数型プログラミングの定義」を説明できる:
関数型プログラミングとは、純粋な関数を書いて隠れた入出力をなるべく取り除き、できるだけ多くのコードを入力と出力の関係だけで記述することだ。
ほとんどのプログラムは何かを返すというより何かを行うために実行されるので、どうしたっていくらかの副作用は避けられないけど、プログラムの中は厳格に制御しよう。副作用(と副原因)を可能な限り排除して、それでも取り除けない場合は厳重に管理するんだ。
別の言い方をするなら、「コード片に必要なものや、コード片が生成する結果を隠蔽するのはやめよう」ってことだ。コード片を正しく実行するのに必要なものがあるなら、そう言おう。コード片が何か有益なことをするなら、それを出力として宣言しよう。そうすれば、僕らのコードはよりクリーンになる。複雑性を表層に移せば、それをかみ砕いて対処することができる。
「関数型プログラミング言語」って何?
あらゆる言語は純粋関数をサポートしている ― 例えば add(x, y)
を非純粋関数にするのは難しい。*1そして多くの場合、非純粋関数を純粋関数に変換するには、関数の入力と出力をすべてシグネチャに持ち上げるだけでいい。そうすれば、シグネチャは関数の挙動を完全に記述するようになる。じゃあ、あらゆるプログラミング言語は「関数型」なのか?
そんなことはない。もしそうなら、「関数型プログラミング言語」なんて言葉は無意味だってことになる。
それじゃあ、「職業プログラマ向けの関数型プログラミング言語の定義」はなんて説明すればいいんだろう?
関数型プログラミング言語とは、副作用なしでプログラミングすることをサポートしたり奨励したりするような言語だ。
より具体的には、関数型言語は副作用の積極的な排除を可能な限り助け、それができない場合は厳重に制御するのに役立つ。
より過激な言い方をするなら、関数型言語は副作用に対して積極的に敵対する。副作用は複雑で、複雑さはバグで、バグは悪魔だ。関数型言語は君が副作用に敵対することも助け、君と共に副作用を力で屈服させるだろう。
それで全部?
そうだ。きっとこれまで、君はそれが隠れた入力であると考えたことがないような微妙なものもいくつかあるだろうけど、それが本質だ。「副作用が第一の敵である」という観点でソフトウェアを構築し始めると、君がプログラミングについて知っているあらゆることが変化するだろう。この記事のパート 2 では、副作用と関数型プログラミングについて把握した上で、プログラミングの現状に散弾銃をブッ放すことにしたい。
謝辞
この記事は、関数型プログラミングの性質について何回か議論したことが元になっている。特に Sleepyfox との「適切なライブラリを組み合わせれば JavaScript は関数型プログラミング言語とみなせるか」を巡る議論からは大いに刺激を受けた。僕の答えは直感的にはノーだけど、それがなぜかを考えることを通じて、非常に実りの多い思索に誘われるきっかけになった。
James Henderson に敬意を。今年は、彼のおかげで関数型について多くの実りあるアイデアに触れることができた。
そして、Malcolm Sparks, Ivan Uemlianin、Joel Clermont、Katy Moe、そして僕のドッペルゲンガー Chris Jenkins。皆の校正と提言に感謝する。
おまけ: Part 2 の概要
パート 2 を翻訳する元気が残ってないので概要だけ紹介しておきます。
関数型プログラミングは何ではないか
関数型言語は副作用(副原因)と戦うための道具であって、つまり:
関数型言語はどれ?
設計の臭い
- 引数なしは副原因のシグナル
public Int foo() {}
- 戻り値なしは副作用のシグナル
public void foo(...) {...}
ErgoDox の壊れたキースイッチを交換する
この記事は ErgoDox Advent Calendar 2016 の3日目です。今日は、皆さんのご家庭でもできる「簡単、Cherry スイッチの分解手順!」をご紹介したいと思います。
近況
突然の ErgoDox ブームから早9か月、皆さんのキーボードライフはいかがでしょうか? 僕もブームに乗って、当時からこんな記事を書いて情報収集やパーツ集めに勤しんでおります。
あと、meetup で LT もやりましたね。
https://eventdots.jp/report/20160610_588645eventdots.jp
正直なところ、使いこなしに情熱と労力が必要な部類のガジェットですし、商業ベースの分割キーボードが登場し始めた昨今、あえてコレを選ぶ理由も少しずつ減ってきている気もしますが、しかし、我が家にやって来た ErgoDox 君は今日も元気に活躍中です。
現在は、新たに Massdrop で調達した GMK 製キーキャップと O-Ring を組み込んで使っています。段差付きの Cherry profile のキーキャップに取り換えたことで打ちやすさが向上したほか、僕は打鍵が強い方(カチャカチャ、ッターン!)なので、ゴム製の O-Ring を装備して打鍵の衝撃を吸収することで指にかかる負荷を軽減しています。
僕は元々は HHKB Pro Type-S ユーザなのですが、スイッチに赤軸をチョイスしていることもあって、むしろ以前よりも軽快なタイピング環境を実現できています。あのスコスコした打ち心地も捨てがたいんですけどね…。
キースイッチのチャタリング問題
というわけで、一番の目的であった肩凝りも改善されて(最近はむしろ腰痛が…)快適な ErgoDox ライフを送っています。万歳!
…ところが、秋に入った頃から、次第にキーのチャタリング現象 (Contact bounce) に悩まされるようになってきました。
ErgoDox EZ は、キースイッチとして Gateron を採用しています。これは、その名の通り Gateron という会社が供給している Cherry MX クローンのスイッチで、自作キーボード界隈のデファクト的なパーツなのですが、どうもこいつの調子が良くない。
具体的には、特定のキーで打鍵しても入力されなかったり、一回の打鍵で何文字も入力されてしまいます。典型的なチャタリングですね。これ自体は、メカニカルスイッチである以上は避けようがないのですが、数か月で現象が出るのはひどい…。*1
普通の市販のキーボードなら、泣く泣く「買いなおす」の一択ですが、そこは我らの DIY キーボード ErgoDox。この場合、解決策は二つあります。一つはファームウェアでの debouncing 設定の調整、もう一つはスイッチ部品の交換です。
ファームウェアで debouncing する
一つ目は、ファームウェアの debouncing 設定を調整する方法です。
そもそもチャタリングはなぜ起きるのでしょうか? キースイッチは、下記の図のようにキーを押し込んだ時に電極が接触したり離れたりすることで、電圧を変化させる機構になっています。
(from The Keyboard Company's blog)
ファームウェアは、一定間隔でスイッチの電圧をスキャンすることでキーの押下を判定するのですが、スイッチが機械的にヘタってくると電圧の変化が不安定になって、短時間で何回もスイッチを上げ下げしたかのように判定されてしまうことがあります。
これを回避するには、ファームウェア側で「スイッチの押下状態が一定期間(5 ms とか)継続された場合のみ有効な打鍵と判定する」と処理してやるのが有効です。これを debouncing といいます。ErgoDox のファームウェアには DEBOUNCE 定数 が用意されており、これを調整すると問題を軽減できるようです(デフォルト値は 5
)。以下で紹介するキースイッチの交換が難しい場合は試してみると良いかもしれません。ファームウェアをビルドして書き込む方法については前回の記事を参照してください。
(DEBOUNCE 定数の調整については、あまり大きな値を設定すると弊害があります。識者が技術的な解説を GitHub にコメントして下さっていますので、英語ですがご参考までに。)
キースイッチの交換
もう一つは、今回の記事の本題であるキースイッチの物理的な交換です。チャタリングの原因がスイッチの上側の部分(バネ、軸、カバー)にある場合は、これで問題が改善します。
補足(12/17): スイッチのメンテナンスについて「接点復活剤でも改善するのでは?」というご指摘を受けました。下側の電極部分の問題の場合は、こちらを試してみるのも手かもしれません。化学物質なので、例えばプラスティックが溶けるような成分が入ってないか注意しましょう(参考)。直に吹き付けるのもやめた方がいいかも。
補足(2017/10/29): 以下で説明するスイッチ交換ではどうしてもチャタリングが直らなかったスイッチの電極部分に、KURE の エレクトロニッククリーナーとコンタクトスプレーを試したところ改善しました。半年程度の時点では不具合も出ていないので、スイッチ交換の前にこちらを試してみるのも手かもしれません。
僕の場合、メカニカルキーボードの ErgoDox を入手した時点でチャタリングの発生は予見していた *2 ので、Massdrop で Gateron スイッチ単品の出品を見つけた際に予め確保していました。後述するように、EZ に付いてくるものと完全に同じものではないようで、後から入手した方がハウジングが若干タイトでガタツキが少ない気がします(気のせいかもしれないけど。
Massdrop の Gateron は定期的に再出品されるようなので、今は問題がない人でも予備を確保する意味で出品をチェックしておくことをお勧めします(購入確定後1ヵ月くらいで届く。120 個入りで $25 くらい)。また、少し割高ですが、すぐに入手したい場合は Mechanical Keyboards や 1Up Keyboards、Clueboard などの EC サイトで買う手もあります(補足: 国内でオリジナルの Cherry MX スイッチを入手する方法について、記事の最後に追記しました。)(補足2: PCB mount か Plate mount か選べ、と言われる場合がありますが、基本は Plate mount で問題ないです。PCB mount は固定用の追加の出っ張りがあるバージョン)。
あと、チャタリング以外にも、ドイツの GMK electronic design 製のキーキャップは Gateron スイッチと相性問題(下側に力を入れて押下した際にひっかかりがある)がありますが、これもスイッチの交換で直る場合があります。
僕の場合は、実際にスイッチを交換したら問題が解消したので、ロット固有の問題なのか製造時期の問題なのかもよく分からない(製造番号とか書いてないので)のですが、同様の問題に当たった方は試してみる価値はあるかと思います。
工具の準備
では、交換用のスイッチの在庫を確保している前提で話を進めていきます。まず、交換にあたってはいくつか工具を準備する必要があります。
- キーキャップ引き抜き工具
- ピンセット(or マイナスドライバー)
- キースイッチ分解工具
キーキャップ(キートップ)引き抜き工具は、キーボードの清掃用として国内でも容易に入手できます。僕はダイヤテックの販売している FILCO 製のやつを使っていますが、類似の製品はいくつかあるので、どれかを入手すればよいでしょう。
FILCO Keypuller キーボードメンテナンス用キーキャップ引き抜き工具 ブラック FKP01
- メディア: Personal Computers
ピンセットは、あれば便利というレベルで必須ではありません。ホームセンターとかで適当に買ってきましょう。マイナスドライバーとかでもよいですが、先が曲がっているものが使いやすいかもしれません。
問題は、キースイッチの分解 (disassembly) 工具です。Cherry MX 系のスイッチはハウジングが上下で分かれていて、四か所のツメで固定する構造になっています。つまり、このツメを外側に持ち上げてやれば上側のカバーを取り外せます。
ただ、この写真では下からピンセットで持ち上げていますが、実際にはスイッチはキーボードの基盤に半田付けされていて下側からのアクセスは難しいので、上側からカバーの両端の穴から何かを差し込む形になります。ドライバーでこじ開けてもいいんですが、二か所ないし四か所を同時に持ち上げないといけないので、そこそこ面倒です。専用の工具も売ってるんですが、日本在住だと $7 の工具を手に入れるのに国際輸送でプラス $35 とかさすがにちょっと…。
というわけで、自分で分解工具を作ってしまいましょう。幸い、身近な材料で簡単に自作できます。
はい、そこら辺に転がってるふつーのバインダークリップですね。大きさは、あまり太いものを選ぶと加工しにくいし何より穴に入らないので、15 mm くらいの小さめのやつで十分です。
加工も簡単です。クリップを分解して針金の部分を取り外し、曲がっている部分が少しだけ外側を向くようにペンチなどで捻じ曲げてやります。
これだけ。実際に穴に差し込んでみると、こんな感じで先端部分でツメを外側へ持ち上げられます。
交換作業
工具さえ揃えば、あとはお茶の子サイサイ(死語)です。やってみましょう。(言うまでもありませんが、以下の作業は自己責任でお願いします)
まず、引き抜き工具でキーキャップを取り外して、交換対象のキースイッチを露出させます。作業性を考えて、周りのキーキャップも一緒に取り外すとよいです。
次に、先ほどの分解工具を写真のように穴に突き差して、グッと真ん中に引き寄せながら持ち上げると…
このようにパカッと開いて、スイッチの下側の電極だけが残ります。この部分は基盤にはんだ付けされていて、壊すと面倒なので気を付けましょう。
ちなみに、写真のように一気に開けるのが難しい場合は片側ずつ持ち上げても大丈夫。ピンセットで押さえながら持ち上げるとやりやすいです。
最後に、新しいスイッチを分解してバネ、軸、カバーを取り出し、それら三つを元通りに穴へはめ込めば交換完了です。
ね、簡単でしょう?
まとめ
というわけで、キーボードの中で一番故障しやすいスイッチが壊れても自分で直せるようになったので、愛用の ErgoDox をメーカーの供給状態に左右されずに末永くメンテできるようになりました。
まぁ、ここまでの手順を確立するのにかなり時間を費やしたのは確かで、改めて一般人向けのガジェットではないなと思うものの、気になる部分を一つ一つ自分で直したり改良したりできるのは、やはり DIY キーボードの魅力かと。「手がかかるほどかわいい」は真理ですね。…誰だ、沼とか言ったの!
そんなわけで、PFU さんがセパレート式 HHKB を出すまでは当面コレで頑張ってみたいと思います。
追記: Cherry MX の入手方法について
互換品ではない正規の Cherry MX スイッチを国内で入手するルートがあるようです(10 個 770 円)。在庫が不足する場合もあるようですが、売っていたら確保しておくと良いかも。
Cherryの青軸とかはここで買ってるけど、これだとErgoDoxには組み込めないのかな?国内だし3日くらいで届く。→https://t.co/ZO0joMd4fb
— タケダハチオ (@takedahachio) 2016年12月3日
ErgoDox EZ は Gateron ですが、互換性があるので Gateron を分解して Cherry を取り付けることは問題なくできるようです。実際に試した方によると「違和感なし」とのこと。
お分かりいただけただろうか(見づらい
— O.J.I. (@re_OJI) 2016年12月5日
Gateron→CherryMX換装できました。
ErgoDox の壊れたキースイッチを交換する - Okapies' Archive https://t.co/nw5De5Lox5 pic.twitter.com/IgxopjXC2N
@okapies 無事出来ました。Gateron→Cherry全く問題&違和感なしです。https://t.co/mgEN4RUB2U
— 中途半端er (@ChootHumper) 2016年12月5日
ただ、互換品とはいえ異なるメーカーの製品を組み合わせることになるので、あくまで自己責任でお願いします。また、軸によっては色が同じでも荷重が違う場合があるのでご注意。
ErgoDox EZ カスタマイズ情報のまとめ
先日買った ErgoDox EZ が届いたのでカスタマイズ中。GW に発注して一週間くらいで届いたので、かなり流通体制が整ってきている様子。
いぢりがいがあるのは良いのだけれど、正直なところ「素人お断り」感がすごいですね*1。というわけで、調べたことをまとめていこうかと。随時更新予定。
※記事中でリンクしているドキュメント類はかなり頻繁に場所が変わっているので、リンク切れの場合は頑張って探してください…。
ファームウェアのカスタマイズ
追記 (11/27): EZ 公式の GUI 設定ツールが登場しています。日本語キーの割り当てとかは部分的にしかサポートしてないっぽいですが…。
ErgoDox のキーマップを変更するには、本体ファームウェアの更新が必要。ファームウェアをカスタマイズするには、Massdrop の Ergodox Configurator を使うか qmk_firmware を自分でカスタマイズしてビルドする。Ergodox Configurator だと日本語キーやマクロなどが使えないので、その場合はファームウェアのビルドが必須になる。
ビルド手順はプラットフォームごとに整備されているので、詳細はドキュメントを参照してほしい。手順自体は、依存ライブラリをインストールしたら keyboards/ergodox/ に行って make
するだけなので、そこまで難しくないはず…。
キーマップの定数の意味
keymap.c に出てくる KC_XXXX
という定数は、様々なキーコードに対応している。キーをカスタマイズする際は、これを所望のキーコードに書き換えてやる。
公式のドキュメントは以下にある:
- https://github.com/jackhumbert/qmk_firmware/blob/master/doc/keymap.md(キーマップの説明)
- https://github.com/jackhumbert/qmk_firmware/blob/master/doc/keycode.txt(キーコード一覧)
ファームウェアのインストール
Teensy を使ったファームウェアの書き換え手順。リセットスイッチを押すのに細長いものが必要なので、手元にゼムクリップを用意しておくとよい。
Hyper キーと Meh キー
デフォルトレイアウトにある Hyper キーと Meh キーってなんやねん、と思って調べてみた。これは、通常の方法では入力できない修飾キー (Alt + Ctrl + Shift + Cmd) を組み合わせることで、起動中のアプリケーションと被らない、どのタイミングでも使用できるホットキーを設定できる、というものなんだそうだ(追加でキーボードショートカットを設定するユーティリティアプリが必要)。例えば、エディタを操作してる最中に Hyper + S で Slack アプリを呼び出す、みたいなことができる。Meh キーは Cmd キーを削った Windows 向けのバージョン(Windows に Cmd キーはないから)。
元々、OS X で疑似的に Hyper キーを実現するというテクニックがあって、それをキーボード自体に組み込んだものらしい:
- A Modern Space Cadet / Steve Losh
- A useful Caps Lock key - BrettTerpstra.com
- Using Slate: A Hacker's Window Manager for Macs - Tristan Hume
JIS(日本語)レイアウト固有のキーについて
ErgoDox の初期設定は US レイアウトを前提にしているので、JIS レイアウトで使うには、例えば「半角/全角」キーは自分で割り当てる必要がある。
JIS レイアウトでの定数とキーの対応関係は、下記の記事にまとまっている:
仕様とかの解説は以下:
- USキーボードと日本のキーボードの違い(US レイアウトにあって JIS レイアウトにないキー等の解説)
- USB キーボードのキーコード仕様 (pp.53-60)
- USBキーボードのキーコード
OS X で日本語関連のキーが動作しない問題があるようだ(正確には、OS X で動くように修正すると逆に Windows や Ubuntu で動かなくなる)。解決してメインラインにマージされたっぽい(255
が signed
で -1
だったぜとか書いてあってマジかってなった)。
ファームウェアの CI 環境
自宅以外の環境でも調整できるようにしたかったので、id:ymotongpoo さんのブログ記事を参考にして CI 環境を構築した。qmk_firmware のビルド手順は頻繁に変わっていていちいち対応するのも大変なので、普通はここまでする必要はないと思う。
QMK の公式レポジトリに追随したかったので、直接 fork して develop
ブランチを切った。
僕が使っているファームウェアは以下からダウンロードできる(キーマップ):
やっていることは、依存ライブラリをインストールした Docker コンテナで make
しているだけ。最新バージョンに対応した wercker.yml は GitHub に置いてあるので、もし自分で CI 環境を組む際はどうぞ(wercker 側で $TARGET_KEYMAP
環境変数の設定が必要)。Docker Image を自分で用意する場合はこの Dockerfile を使ってください。
キーキャップ
ErgoDox EZ は Cherry MX キースイッチなので、Cherry 用のキーキャップを流用できる。ただし、ErgoDox は親指キーや周辺キーのサイズが通常のキーボードと異なるので、この部位に対応する特殊なキーキャップの入手が課題になる。必然的に特注品になってしまうので高価なことが多い。
ErgoDox 用キーキャップ
前述の通り、ErgoDox のキーキャップをカスタマイズする場合は、特殊なサイズのキーキャップを揃える必要がある。必要数は以下の通り(参考1、参考2):
- 1x: 60 個(普通のキーと同じサイズ)
- 1.5x: 12 個(Tab キー程度のサイズ。両脇に配置)
- 2x: 4 個(シフトキー程度のサイズ。親指部分に配置)
また、段差付き(=各段で形状が異なる)の sculptured のキーキャップの場合は、各段 (row) ごとに個数を合わせる必要がある。以下は一例(参考):
- 16 - Row 1 - 1u
- 14 - Row 2 - 1u
- 10 - Row 3 - 1u
- 20 - Row 4 - 1u
- 2 - Row 1 - 1.5u
- 2 - Row 2 - 1.5u
- 6 - Row 3 - 1.5u
- 2 - Row 4 - 1.5u
- 4 - Row 2 - 2u
段数の数え方はキーキャップのメーカーによって異なるので注意してほしい。
プロファイル
市場に出回っているキーキャップには様々な形状があり、形状ごとに「○○プロファイル (profile)」という名前で呼ばれている。プロファイルの詳細については、Reddit の Wiki や Geekhack のスレッドなどが詳しい。代表的なものには以下がある:
ErgoDox EZ 公式で買えるのは、文字なしで段ごとに形状の異なる (sculptured) の DCS プロファイルと、文字入りで上面が真っ平な代わりにキャップ同士の入れ替えが可能な DSA プロファイルの二つ。
- Our Keycaps | ErgoDox EZ
- Finishing up the ErgoDox - adereth(各プロファイルの断面図が載ってる)
11月頃?からキーキャップの単品売りもしてくれるようになったが、かなりいいお値段 ($90) なので本体とセットで買った方が良い(これでも相場的には安い方だったり…)。
JIS 配列の文字刻印が入っているキャップは、現状では下記の FILCO が販売している製品しか入手できない。
FILCO Majestouchシリーズ専用交換用キーキャップセット 日本語108キー(91キー兼用) カナなしモデル ブラック FKCS108NB
- 発売日: 2014/12/18
- メディア: Personal Computers
当然、ErgoDox 用の 1.5x や 2x のキャップは含まれていないので EZ 付属のキャップと組み合わせる必要がある。しかし、これは OEM プロファイルというかなり背丈が高いプロファイルで DSA プロファイルとは組み合わせにくい(DCS もおそらく同様)。そこを我慢して使うならアリか。
材質
ABS 樹脂製と PBT 樹脂製の二種類がある。基本的には PBT の方が高級で、耐久性が高く摩耗によるテカリが出にくいが、加工しにくいという難点がある。そのため、特にデザイン性の高いキーキャップには ABS 製が多く、PBT 製は刻印のない無地のキーキャップに多い。
また、キートップに印刷されている刻印が摩耗で消えてしまう問題があるが、"double-shot" と呼ばれる二色成形のキーキャップなら回避できる。
入手方法
国内ではほぼ入手できないので、海外からの輸入が基本になる。
キーキャップメーカーの詳細は下記の記事が詳しい:
キーキャップをバラ売りで欲しい場合は、Pimpmykeyboard.com や WASD Keyboards などのオンラインショップから購入できる*2。色や形をカスタマイズしたい場合は利用することになるだろう。その他の取り扱い店の情報はこちら。
自作キーボード界隈では、Massdrop に代表される共同購入 (group buy) サイトで不定期に募集される特注品のキーキャップを購入することも一般的に行われている。普通は通常のキーボード用ばかりだが、稀にオプションで ErgoDox 用セットが提供される場合がある。いずれの募集 (drop) も一週間程度で募集が締め切りになるので、定期的にチェックしておくとよい。2ch のキーキャップ総合スレでは Massdrop 以外の共同購入の情報も共有されている(物によっては品質が怪しい場合もあるようなので注意)。
WASD Keyboards では、キーキャップに好きな文字をプリントできるオプションを提供している(1個あたり7ドル)。お金があるなら JIS 配列を刻印したものを作ってもらう手も…?
ケーブル
この項については ErgoDox users meet up で発表した。
仕様
ErgoDox は、マシン本体との接続に使う Mini USB の他に、セパレートしたキーボード同士を接続するための 直径 3.5mm の TRRS ケーブルが必要。これは、国内では「4極ステレオミニプラグ」と呼ばれてオーディオ用で流通しているものと同じもの。
2極や3極との見分け方は、コネクタ部分が3本の線で4つに分かれているかを調べる。
市販品
ErgoDox EZ に付属してくる TRRS ケーブルは長めで無骨な感じのものであり、できればもっといい感じのものに買い替えたい。が、4極のオス/オスでケーブル長が短めのもの、という製品は数が限られる。
現状では、ダイヤテックが販売している Matias Ergo Pro 用のケーブルを流用するのがおすすめ。巻き取り式なので長さの調節が容易なのが良い。
あと、こういうのもあるが、買ってみたところかなり短いので広げて配置したい人には厳しそう。
エスエスエーサービス [ 3.5mm4極ステレオミニケーブル ] オス - オス [10cm] ST35-AM01R4
- 発売日: 2016/03/03
- メディア: エレクトロニクス
オーダーメイド
ErgoDox 向きの素敵な TRRS ケーブルが不足しているのは海の向こうでも事情は同じらしく、自作しちゃう人もいるようだ。
そんな需要に応えてなのか、ケーブルをオーダーメイドで作ってくれるサービスがあり、色とか長さとかを自由に選べる。日本への配送もやってくれるっぽいが、送料込みで四~五千円くらいするのが難点。
CPU 切替器(KVM スイッチ)
今までサンワサプライ製の CPU 切替器を使っていたんだけど、どうやら ErgoDox とキーボードエミュレーション機能の相性が悪いらしく、差しても Windows で認識されない。*3
以下、手に入る範囲でいくつか試したので紹介する(6/10 現在)。現在は ATEN の製品を使っていて、マウスキーが使えない以外は切り替え時のラグもなく満足している。
メーカー | 製品名 | Linux (Ubuntu) | Windows | マウスキー | 備考 |
---|---|---|---|---|---|
サンワサプライ | SW-KVM2LU | ◯(即時) | × | ? | ディスコン。後継機は SW-KVM2LUN |
バッファロー | BSKMRA201 | ◯(ラグ小) | △(ラグ大) | ◯ | - |
RATOC | REX-430UDA | ◯(ラグ小) | △(ラグ大) | ◯ | - |
ATEN | CS692 | ◯(即時) | ◯(即時) | × | - |
ATEN は国内メーカーに OEM 供給しており、中身は ATEN 製というものがけっこうある。ATEN 自身のブランドで販売されているものは、ファームウェアの更新に対応している点がメリット。サンワサプライ製品も ATEN OEM が多いので、最近の製品でファームウェアが最新になっているなら使えるかもしれない。
ScalaMatsuri 2016 でスタッフ&発表やりました
だいぶ出遅れましたが、ScalaMatsuri 2016 の参加報告です。
発表者として
今回、CFP に応募して当選した「なぜリアクティブは重要か (Why Reactive Matters)」というタイトルで発表をさせていただきました。投票してくださった皆様、聞きにきてくださった皆様、ありがとうございます。
発表に使ったスライドは以下になります(発表は英語スライドでしたが日本語化しました):
www.slideshare.net
今回はプログラミングの話題に重点を置きつつ、〈リアクティブ・コンポーネント+データフロー〉という構図が、非同期プログラムのコードから分散システムのアーキテクチャまで、様々なスケールで出現する様子をご紹介しました。
最後でチラリと触れた「イミュータブル・インフラストラクチャ (Immutable Infrastructure) みたいなキーワードが流行ったわりには、DevOps のツールチェインって命令型ばっかで関数型の知見が全然入ってないよね」という話は、そのうちどこかで改めて扱いたいと思っています。
あと、タイトルの元ネタである「なぜ関数プログラミングは重要か」自体についてはあまりちゃんと紹介できませんでしたが、先日、解説記事を書きましたのでご興味があればぜひ。
81 枚分の内容を 40 分の時間内に収めて話すという所はクリアしたものの、「内容が頭に入ってこなかった」というご意見も頂いており、そこは反省点ですね。精進します。
そろそろ、怒涛のスライドストリームをバックプレッシャー無しで叩きつけるスタイルは何とかした方が良い感はある。
— Yuta Okamoto (@okapies) February 1, 2016
参加者として
当日はスタッフとしてバタバタしてたので頻繁に中座してましたが、聞きたかった発表については翻訳チーム内でローテーションを調整してもらったのでガッツリ聞けました。ありがとうございます。
- 1日目:
- リアクティブ・マイクロサービス (Christopher Hunt)
- レジリエンスが無ければ、他は無いも同じ (Jonas Bonér)
- The Zen of Akka (Konrad Malawski)
- 実戦Scalaz-Stream: 既存のバッチシステムを置き換える (Mathias Sulser)
- 2日目:
- アンカンファレンス朝会
- Kafka Tuning (@xuwei_k)
- Async Testing in ScalaTest 3.0 (Bill Venners)
- Typesafeの人にリアクティブについて聞こう(パネルディスカッション)
- パネルディスカッション:Scala社内教育
一日目は、とにかく Jonas Bonér さんの発表が圧巻でした。スライドだけ見ていると「???」という所が多いのですが、実際に聞くと本当に分かりやすい。あと、スライドは字幕付け作業の一環で事前に目を通していたのですが、まさか冒頭で「○ッキー」のテーマをブッ込んでくるとは思わなかった(笑。
〈レジリエンス〉というキーワードについては、今回、翻訳チーム内でもどう訳すかについて散々議論したのですが、日本語にピッタリと対応する言葉がない、なかなか奥深い概念です。リアクティブ・マニフェストの翻訳では〈耐障害性〉としたのですが、そのまま〈レジリエンス〉と訳すより仕方がないという結論になったので、マニフェストの方も変更するかもしれません。そのうち講演動画もアップする予定なので、その際に私も解説記事とか書きたいですね。
二日目は、朝会に始まり Typesafe メンバーのパネルと Scala 社内教育のパネルと、一日に三回も司会に登壇するという自分でも謎の大車輪をやっていました。貴重な経験ができたと思います(笑。
来日中の Typesafe メンバー全員に参加していただいたパネルディスカッションについては、まぁぶっちゃけると私がどうしても話を聞きたかったので、麻植さんに出演交渉の仲介をお願いしたり、TIS の前出さんと根来さんにも加わって頂いて作戦会議したりして、なんとか形になりました。皆様、本当にありがとうございます。
内容としては、Typesafe 社の技術開発やビジネスの方針について、今までドキュメントやプレスリリースから憶測していた部分のニュアンスがかなり明確になったので、個人的には大満足でした。今までウェブ上で明言されてなかった話とかも飛び出したし…。
社内教育パネルについては、そもそもの「何を、どういう順で教えるべきか」という点が人によって意見に隔たりがあることが確認できたものの、できればもう少し深堀りできると良かったですね。司会として、事前にインタビューとかしておくべきでした。
スタッフとして
今回、準備委員会では「翻訳チーム」のリーダーを任せていただきました。仕事内容はこんな感じですか:
- 同時通訳者の派遣の手配と、当日の対応
- メールやリリースの文面の翻訳・レビュー
- CFP の英訳・日本語訳とウェブサイト掲載
- 発表スライドの CoC compliance レビュー、英文レビュー、字幕作成、etc..
- 海外参加者の渡航に関するフォロー
今回、同時通訳はケイワイトレードさんにお願いしました。PyCon の方から良い評判を伺っていたことが決め手になって依頼したのですが、当日ご来場頂いた方はお分かりの通り、非常に質の高い通訳を提供して頂けました。
これは、通訳者の皆さんがプログラミングについての知識をしっかりとお持ちであったことが大きいです。また、発表者に対するインタビューなどの事前準備もしっかりしていました(自分がインタビューを受けた際も、「アクターは、オブジェクト指向で言うところのオブジェクトと同じものですか?」という的確な質問を受けて感心しました)。今回の ScalaMatsuri は、関数型プログラミングの専門用語が飛び交うたいへん難しい依頼だったと思いますが、期待以上の仕事をして頂けたと思います。
翻訳の実務については、今年も横田さん (@eed3si9n_ja) に大きく依存してしまい申し訳ありませんでした…。ただ、今年は皆様のご協力のおかげで、去年よりもワークロードが分散化できたかなと思っています。竹井さん (@taketon_)、木村さん (@kimutansk)、田中豪さん (@tan_go238)、田中翔さん (@tshowis)、岡田さん (@ocadaruma)、大村さん (@everpeace)、青山さん (@aoiroaoino)、河内さん (@kawachi)、その他ご協力頂いたスタッフの皆さん、ありがとうございました(順不同)。
行動規範 (CoC) について
昨年から始めた行動規範の文面の見直しや、マナー動画のアイデア出し、各種資料の compliance レビューに参加しました。
行動規範のあり方についてはスタッフ内にも色々な考え方がありますが、個人的には規範的な面より啓発的な面を重視すべきという考え方で意見をしていました。要は、ルールを定めるのは「開かれたカンファレンス」の実現が目的であって、違反者の断罪が目的ではないということです。
Scala Matsuri は、様々な地域やコミュニティから集う技術者に対して開かれたカンファレンスを目指しています。特に、性別や人種など、多様な背景を持つ人々が互いに敬意を払って楽しい時間を過ごせるよう、当カンファレンスでは、発表者や参加者、スポンサーの皆様に以下の行動規範を守っていただくようにお願いしています。
また、こうした取り組みは我々だけがやっても意味がないので、他のカンファレンスにも輪を広げていきたいと思っています。今回の行動規範の文面やマナー動画は、皆さんのカンファレンスでどんどんパクって頂くことを前提に作っています。ご自由にお使いください。
一方で、行動規範を真面目に運用しようとすると、資料のレビューやクレーム対応等に少なからずコストをかける必要があるのも確かです。技術カンファレンスの多くがボランティアで運営されている以上、いきなり完全なサポートは難しい場合も多いと思います。
私は、段階的な導入で構わないと思います。行動規範は、先ほども書いたように、「正義の実現のため」というよりも「参加者の間口を広げるため」にあると考えるからです。また参加者の方も、自分が参加するカンファレンスに行動規範がない場合に、そのことを責めるのではなく、自分から手を挙げて仕組みの整備に協力するくらいの心構えを持って頂けると良いのではないかと思います。
まとめ
2013 年の Scala Conference in Japan 以来、3回ほどスタッフとして関わってきましたが、国内外の濃い面子がここまで一堂に会する機会を実現できたのは始めてだと思います。個人的には、他の技術カンファレンスを通して考えても例がありません。例えば、吉田さん(Scalaz コミッター)と Bill Venners さん(Scalatest 作者)の対話が実現した件は、その最良の成果の一つだと思います。
また、同時通訳については導入して本当に良かったと思います。私も含めて、数年前よりも英語スキルが上がってきている面子が増えているものの、双方向に議論をするとなると、まだブ厚い壁が存在します。その垣根が取り払われることで、日本語話者と英語話者が同時に登壇してパネルディスカッションをやる、といったことも実現できるようになりました。「Scala の国際カンファレンスをやる」という目標に対して、大きく近づいたと思います。
参加者の皆さん、発表者の皆さん、そしてスタッフの皆さんの貢献に感謝します。ありがとうございました。
聞きたかった話が全部聞けたし、自分の意見を人に聞いてもらう機会も貰えたし、今まで参加した中で一番密度の濃いカンファレンスだったと思う。皆さんの参加とか貢献があってこそ実現した機会だと思うので、とにかく感謝感謝ですね。 #ScalaMatsuri
— Yuta Okamoto (@okapies) January 31, 2016
ReactiveSocket について
この記事は、Java Advent Calendar 2015 の 22 日目です。前日は、n_slender さんの「PlayFramework 2.4 Java Ebeanでのアプリ開発」でした。
今日の記事では、この半年くらいで仕様と実装が出てきている ReactiveSocket というプロトコル仕様についてお話したいと思います。
なぜ Java Advent Calendar でプロトコルの話を? と訝しがっている方も多いと思いますが、基本的には以下の二つの理由です。
- JEP 266 として JDK 9 に追加される予定の Reactive Streams と密接に関わっている
- Java 製のサーバサイド向けライブラリを多数 OSS 化している Netflix が中心になって仕様策定を行っており、参照実装も JVM 向けが中心
予定ではプロトコルレベルの話にも踏み込んで解説したいと思っていたのですが、プライベートが色々と立て込んでいるため、概要レベルのご紹介になることをお許しください。
ReactiveSocket って何?
ReactiveSocket is an application protocol providing Reactive Streams semantics over an asynchronous, binary boundary.
(ReactiveSocket とは、非同期バイナリ境界をまたいで Reactive Streams のセマンティクスを提供するアプリケーションプロトコルである。)
ざっくり言うと Reactive Streams の考え方をアプリケーションプロトコルのレイヤで実現するための仕様。
そもそもの Reactive Streams とは何か、については以前に書いた記事でも解説しているのでご参照ください。
要点としては、メッセージ駆動のコンポーネント間でメッセージをやり取りするシステムを組んだ際のフロー制御の方法を定めている。
具体的には、送信側が受信側の処理能力を超える量のメッセージを送信してバッファを溢れさせることのないように、受信側から送信側に対して「次は◯個送っていいよ」というフィードバック (back-pressure) を通知することで、過負荷の際に処理能力を超えるメッセージを受信してシステムがクラッシュする事態を回避することを狙っている。詳細な動作については、以下のスライドの図も参考にしてほしい:
特徴
ReactiveSocket の特徴は以下の通り:
メッセージ駆動
(HTTP2 と同様の)非同期なメッセージ駆動であり、全ての通信は、単一のコネクション上に多重化されたメッセージストリームを介して行う。また、これによってレスポンス待ちでブロックすることがなくなる。
相互作用モデル
ReactiveSocket は複数の相互作用 (interaction) モデルをサポートしている。ユースケースごとに適切なモデルを選んで使用することで、性能やユーザ体験に与える影響を向上できる。
また、後述するようにトランスポートに何を使うか (TCP, WebSocket, Aeron, ...) に依存しないので、これらを使ってアプリケーションを実装すれば、性能特性に合わせてトランスポートを入れ替えることもできる。
- Fire-and-Forget(撃ちっぱなし)
- レスポンスが必要ない場合は、これを使うのが一番効率的
Future<Void> completionSignalOfSend = socketClient.fireAndForget(message);
- Request/Response(単一レスポンス)
- 普通のリクエストレスポンス。「レスポンス1個のストリーム」を最適化したものと考えることができる
Future<Payload> response = socketClient.requestResponse(requestPayload);
- Request/Stream(有限個の複数レスポンス)
- 「コレクション」や「リスト」に相当
Publisher<Payload> response = socketClient.requestStream(requestPayload);
- Topic Subscription(無限個の複数レスポンス)
- 「プッシュ通知」や「イベントストリーム」に相当
Publisher<Payload> response = socketClient.requestSubscription(topicSubscription);
- Channel(双方向ストリーム)
- クライアント側から途中でリクエストの条件を変更したりするような場合に用いる
Publisher<Payload> output = socketClient.requestChannel(Publisher<Payload> input);
フロー制御
二つのフロー制御方式をサポートしている。どちらも、トランスポートレイヤではなくアプリケーションレベルの流量制御に焦点を置いている。
一つは、Reactive Streams が仕様化しているような request(n)
の非同期プル。こちらは、リクエスト発行側 (requester) から応答側 (responder) へのキャパシティの通知に使う。
もう一つは ReactiveSocket 独自のリース (leasing) という仕組みで、応答側から発行側へのキャパシティ通知に用いられる。リースは、「規定時間 (TTL) までに◯個まで送ってよし」という形式でリクエストを発行する。これによって、データセンター内のサーバ間通信のようなユースケースで、アプリケーションレベルの負荷分散(クライアント側で、各サーバから通知されたリースの情報を使ってリクエストを分散する)がやりやすくなる。
多言語 (polyglot) サポート
相互作用モデルとフロー制御を言語非依存なプロトコルとして定義しているので、言語を跨いだインタラクションに利用できる(Reactive Streams は JVM 上で動作するミドルウェア同士でしか利用できない)。
様々なトランスポートレイヤをサポート
ReactiveSocket 自体は OSI Layer 5/6 相当のアプリケーションプロトコルであり、TCP 以外にも WebSocket や Aeron (*)、Quic といった様々なトランスポートプロトコルの上に実装できる。
また、ReactiveSocket が定義するアプリケーションレイヤはトランスポートの差異を隠蔽するので、ユースケースに合わせて最適なトランスポートを選ぶことができる。
*: Reactive Manifesto の執筆者の一人である Martin Thompson の会社 Real Logic が開発しているトランスポートプロトコル。元 LMAX の CTO で Disruptor を開発していた御仁、といえば分かる方もいるのでは。
性能
コネクションを使い回すので、コネクションを何度も張り直すような余計な処理を回避できる。また、バイナリプロトコルなので CPU 負荷を削減できる。さらに、フロー制御が組み込まれているので、相手先システムがスローダウンしている時にリトライ地獄を仕掛けてさらに負荷をかけるようなことがない。
同様の課題を解決する仕組みとして、Netflix が自身のマイクロサービス同士のフロー制御に使っている Hystrix があるが、オーバヘッドや複雑さが増すという問題点があった。
なんで HTTP/2 を使わないの?
大雑把に言うと、HTTP/2 は一義的にウェブサイトからドキュメントを取得するブラウザのためのプロトコルで、ReactiveSocket が想定するユースケースに合わないから。
- リクエスト/レスポンスのみで、それ以外の相互作用モデルをうまくサポートできない
- アプリケーションレベルのフロー制御の仕組みがない
- REST は非常に普及しているが、アプリケーションのセマンティクスを定義するのに使うのは非効率で不適切である
対応実装と今後について
コアライブラリとして reactivesocket-java が公開されている。これ自体はプロトコル実装を Reactive Streams API でアクセスできるようにしたもので、実際には以下のような具体的なトランスポートプロトコルの実装でラップして使う:
また、ブラウザや Node.js から使える JavaScript 版の実装も作られている:
- reactivesocket-js
- reactivesocket-js-ws(今のところ空っぽ)
今後…については最近あまり追いかけられてないので分からないです。すいません。とりあえず、Netflix 内部のフロー制御を Hystrix から置き換えていきたいのだとは思われる。具体的な進捗を知ってる人がいたら教えて下さい。あとは、Reactive Streams に参加してる他のベンダー(Typesafe とか)が乗っかるのかどうか(いちおう呼びかけはなされていて、Typesafe の人も関心はあるみたい)。
既に見たように、Reactive Streams 自体は JVM に閉じた仕組みだったところを、アプリケーションレイヤープロトコルとして仕様化することで多言語で活用できる可能性が出てきたわけで、個人的には注目しています。
「なぜ関数プログラミングは重要か」を要約してみた(その1)
関数型プログラミング (functional programming) の利点を説く際によく持ち出されるのが、QuickCheck の開発者の一人である John Hughes が 1984 年に著した論文 "Why Functional Programming Matters" だ。「なぜ関数プログラミングは重要か」という題名で日本語訳もされているので、読んだことがある人も多いと思う。
要旨としては、冒頭の1章および2章で述べられている「関数型プログラミングが優れているのは、高階関数と遅延評価という、モジュール同士を貼り合わせる強力な『糊』を持っているからだ」という話がほぼ全てで、以降はそれを具体例に基づいて説明する構成になっている。ただ、その具体例として「数値計算アルゴリズム」やら「ゲーム用人工知能アルゴリズム」やらの話が延々と続くし、しかもコード例が Haskell の先祖にあたる Miranda という言語で書かれているのでなかなか取っ付きづらい。
今回、来年の1月に ScalaMatsuri で「なぜリアクティブは重要か」というお題で話をさせて頂けることになったこともあって、少し頑張って、元ネタであるこの論文を通読したので要約を公開したいと思う。なお、コード例は Scala で書いた(Scala 版のコードはこの記事を参考にしている)。
1. イントロダクション
- 本論文の目的は、関数型プログラミングの重要性を示すと共に、その利点を明確にしてフル活用できるようにすること
- 関数型プログラミングでは、プログラム全体を関数だけで構成する
- メインプログラム自身が関数であり、プログラムへの入力を引数として受け取り、結果をプログラムの出力として供給する
- メイン関数はさらに多くの関数を使って定義されるので、プログラムの最下層に至るまで関数は言語のプリミティブとなっている
- 関数型プログラミングの「利点」:
- 関数型プログラムには副作用(≒代入文)がないのでバグが減らせる
- 参照透明なので実行順序を気にしなくてよく、式をどの時点で評価してもよいのでプログラムをより数学的に扱える
- それはそうなんだけど…
- 「〜ではない」についてばかり語っている(代入文がない、副作用がない、制御フローがない)
- 「〜である」について語らないと、物質的な利益に興味がある人にはピンと来ないだろう
- 関数型プログラミングの力を語るだけでなく、それが目指す理想を示さねばならない
2. 構造化プログラミングとの類似
- 関数型プログラミングと構造化プログラミングを比較してみる
- 構造化プログラミングとは、「goto 文を含まず」「ブロックが複数の入口や出口を持たない」
- さきほどの関数型プログラミングの「利点」と同様に、否定形の説明になっている
- 「本質的な goto」のような実りのない議論の温床になった
- 構造化プログラミングの核心はモジュール化であり、大きな生産性向上をもたらす
- 小さなモジュールは素早く簡潔にコーディングできる
- 汎用モジュールの再利用によって、プログラムをより速く開発できる
- モジュールは独立してテストできるので、デバッグが容易になる
- goto は小規模プログラミングでしか役立たないが、モジュール化設計は大規模プログラミングにおいても役立つ
- プログラミング言語が問題をモジュール化する能力を高めるには、モジュール同士を貼り合わせる糊が重要
- 問題を部分問題に分割し、部分問題を解き、その解を合成する。つまり、問題を分割する方法は、解を合成する方法に依存する
- 例: 椅子を部品(座部、脚、背もたれなど)に分けて作れるのは、ジョイントや木工接着剤があるから。さもなければ、一つの木の塊から椅子を掘り出すしかない
3. 関数の貼り合せ
この章では、二種類の糊の一つ目である「高階関数」について紹介している。sum
のような単純な関数を、高階関数とその引数の組み合わせとしてモジュール化することで、reduce
のような汎用的な関数を導出する。
論文では、二つのデータ型(リストとツリー)に対して適用できる高階関数について述べている。まず、リスト操作関数の汎用化を進めて、最終的に reduce
関数と map
関数を導出する。次に、木(ツリー)構造に対する操作についても同様に redtree
と maptree
を導出する。
章の最後では、「汎用の高階関数と特有の特殊関数の組み合わせとして部品化することで、たくさんの操作を容易にプログラムできる」「新たなデータ型を定義したときは、それを処理する高階関数を書くべきだ」と結んでいる。
リスト編
リスト処理の問題を例に説明する。リストのデータ構造を(Scala で)書くとこうなる:
sealed trait ListOf[+X] case object ListNil extends ListOf[Nothing] case class Cons[X](head: X, rest: ListOf[X]) extends ListOf[X]
以上のデータ構造を使って具体的なリストを表すとこうなる:
[] は ListNil [1] は Cons(1, ListNil) [1, 2, 3] は Cons(1, Cons(2, Cons(3, ListNil)))
次に、リストの要素を足し上げる関数 sum
を定義してみる:
def sum: ListOf[Int] => Int = _ match { case ListNil => 0 case Cons(num, list) => num + sum(list) }
この定義を調べると、sum
に固有なのは初期値 0
と演算 +
だけなのが分かる*1。つまり、sum
は
- 一般的な再帰パターン(
reduce
と呼ばれる) sum
固有の部分(0
と+
)
の二つにモジュール化して、後で貼り合わせることでも作ることができる:
def add(x: Int, y: Int) = x + y def reduce[A, B](f: (A, B) => B, x: B)(list: ListOf[A]): B = list match { case ListNil => x case Cons(a, l) => f(a, reduce(f, x)(l)) } def sum: ListOf[Int] => Int = reduce(add, 0)
(注: Scala でこの書き方をするとスタックが溢れるけど、回避方法は色々なところで紹介されているので略。あと、カリー化の話も略。)
reduce
は(初期値と演算を入れ替えるだけで)様々な用途に再利用できる:
// リストの全要素の積 def product: ListOf[Int] => Int = reduce(multiply, 1 ) // リストの要素のいずれかが true か調べる def anytrue: ListOf[Boolean] => Boolean = reduce(or, false) // リストの全要素が true か調べる def alltrue: ListOf[Boolean] => Boolean = reduce(and, true )
ところで reduce
は、リストの Cons
の部分を f
で、ListNil
の部分を a
で置き換えたものと看做せる:
l = Cons(1, Cons(2, Cons(3, ListNil))) reduce(add)(0)(l) = add(1, add(2, add(3, 0))) reduce(multiply)(1)(l) = multiply(1, multiply(2, multiply(3, 1)))
つまり、reduce(Cons, ListNil)
は(Cons
を Cons
に、ListNil
を ListNil
に置き換えているだけなので)リストからリストを複写する関数とみなせるし、リスト a
と b
に対して reduce(Cons, b)(a)
は二つのリストを連結する関数となる:
def copy[A](l: ListOf[A]) = reduce(Cons[A], ListNil)(l) def append[A](a: ListOf[A], b: ListOf[A]) = reduce(Cons[A], b )(a)
次に、リストの全ての要素を2倍したリストを返す関数 doubleAll
は、doubleAndCons
関数を使って以下のように定義できる:
def doubleAndCons(num: Int, list: ListOf[Int]) = Cons(2 * num, list) def doubleAll(l: ListOf[A]) = reduce(doubleAndCons, ListNil)(l)
doubleAndCons
関数は、以下のように double
関数と fAndCons
関数の組み合わせに置き換えられる:
def double(n: Int) = 2 * n def fAndCons[A, B](f: A => B)(el: A, list: ListOf[B]) = Cons(f(el), list) // 2 * num => f(el) def doubleAndCons: (Int, ListOf[Int]) => ListOf[Int] = fAndCons(double)
ところで fAndCons
関数は f
と Cons
を合成した関数 (Cons . f
) として定義することもできる:
def fAndCons[A, B](f: A => B): (A, ListOf[B]) => ListOf[B] = (Cons[B] _).compose(f)
(注: 上記を実行するには、あらかじめ以下の暗黙変換を定義して import RichFunction2._
しておく必要がある。)
implicit class RichFunction2[T1, T2, R](f: Function2[T1, T2, R]) { def compose[A](g: (A) => T1): (A, T2) => R = (x: A, y: T2) => f(g(x), y) }
したがって、doubleAll
関数は double
, Cons
, reduce
の組み合わせで定義できる:
def doubleAll: ListOf[Int] => ListOf[Int] = reduce((Cons[Int] _).compose(double), ListNil)
ここで、double
関数をパラメータ化すると、リストの全要素に f
を適用する map
関数を導出できる:
def map[A, B](f: A => B): ListOf[A] => ListOf[B] = reduce((Cons[B] _).compose(f), ListNil) def doubleAll: ListOf[Int] => ListOf[Int] = map(double)
map
は汎用的に使える有用な関数で、例えば行列(=リストのリスト)の要素を足し上げる関数を作りたくなっても、以下のように簡潔に書ける:
def sumMatrix: ListOf[ListOf[Int]] => Int = sum.compose(map(sum))
ツリー編
ラベル付きの順序付きツリーについて考えてみる。(Scala で)データ構造を書くとこんな感じ:
sealed abstract class TreeOf[A] { def label: A def subtrees: ListOf[TreeOf[A]] } case class Node[A](label: A, subtrees: ListOf[TreeOf[A]]) extends TreeOf[A]
例えば、以下のようなツリーを:
1 o / \ / \ / \ 2 o o 3 | | | o 4
上記で定義したデータ構造で表すとこうなる:
Node(1, Cons(Node(2, ListNil), Cons(Node(3, Cons(Node(4, ListNil), ListNil)), ListNil)))
リストの時と同様に、reduce
と同じ役割を果たす redtree
関数を考えてみる。reduce
は「Cons
を置き換える何か」と「ListNil
を置き換える何か」の二つを引数に取る関数だった。同じ方針で考えてみると、redtree
は Node
と Cons
と ListNil
を置き換えた三つの何かを引数に取る関数になるはずだ。
def redtree[A, B, X](f: (X, A) => B, g: (B, A) => A, a: A)(tree: TreeOf[X]): B = { def redtreeImpl[A, B, X] (f: (X, A) => B, g: (B, A) => A, a: A)(subtrees: ListOf[TreeOf[X]]): A = subtrees match { case Cons(subtree, rest) => g(redtree(f, g, a)(subtree), redtreeImpl(f, g, a)(rest)) case ListNil => a } f(tree.label, redtreeImpl(f, g, a)(tree.subtrees)) }
reduce
と同様に、redtree
を他の関数と組み合わせて様々な関数が定義できる:
// ツリー全体の label を足し合わせる関数 def sumtree: TreeOf[Int] => Int = redtree(add, add, 0 ) // ツリー全体の label のリストを作る関数 def labels[A]: TreeOf[A] => ListOf[A] = redtree(Cons[A], append[A], ListNil)
最後に、ツリー用の map
関数である maptree
を redtree
を使って定義しておく(5章でゲーム用人工知能を実装する際に使う):
def maptree[A, B](f: A => B): TreeOf[A] => TreeOf[B] =
redtree((Node[B] _).compose(f), Cons[TreeOf[B]], ListNil)
インターミッション
ちょっと力尽きたので、今回はここまで。続きはやる気が湧いたら、ということにさせてください…。
論文では、高階関数がプログラムのモジュール化に役立つ理由について「データ型の詳細に関する知識を高階関数の中に局所化できる」と述べているが、これを逆に言うと「特定のビジネスロジックを実装した関数を、それを適用する(データ型の)文脈から切り離せる」ということになる。
で、この考え方を推し進めると、一例として「抽象的な Future」が述べているような、「本番用の非同期実行の文脈」と「テスト用の同期実行の文脈」を同じコードで切り替えるみたいな仕掛けが実現できるようになる、というわけですね。
JJUG ナイトセミナーで Reactive Streams について発表しました
6月24日の JJUG ナイトセミナーで「Reactive Streams 入門」のタイトルで発表させて頂きました。最近話題の Reactive Programming、気がついたら一万人以上が署名している Reactive Manifesto、そして Java 9 で標準化という話が進んでいる Reactive Streams をまとめて俯瞰してみました、という感じの内容になっています。
かなり戦々恐々だったのですが、思いのほかご好評をいただきとてもとてもほっとしています。発表の機会を与えて下さった JJUG スタッフの皆様、会場をご提供頂いたオラクル様、発表を聴いてくださった参加者の方々、ありがとうございました。
発表でも触れましたが、"Reactive" という概念が何を指すかについては大きな混乱があり、様々な論者が異なる定義を提唱しているのが現状です。一方で、そうした定義の背景には、それぞれに体系的な知見や学術的な議論の積み上げがあるのも確かで、その辺をちゃんと掘り下げた解説を書いてみたいなぁ、と思っていました。
そんなわけで、この半年ほど継続的に資料を収集したり、V2 にアップデートされた Reactive Manifesto の翻訳をやったりしていました。構成については、記事を書くことを念頭に以前からぼんやりと考えてはいたのですが、今回の発表準備にあたって参考にした、英語版 Wikipedia の「データフローを記述する宣言的なプログラミングモデル」と「その実行モデルを実装したランタイム」という定義を軸に据えると、Reactive の名を冠した要素技術群をそこそこ総括的に整理できるのではないか、と考えて作ったのがこのスライドになります。
このテーマについては、まだまだ考えるべきことも多そうですし、今後も継続的に研究していきたいと思っています。改めて、今回は貴重な機会を頂きありがとうございました。
余談1
"Back-Pressure" のあたりでカンバン方式とかザ・ゴール(TOC)とかを連想した / “Reactive Streams 入門 #jjug // Speaker Deck” http://t.co/nYj4AJ1LzE
— Terada Yuichiro (@u_1roh) 2015, 6月 25
たしかに、次に使う個数を書いたモノを前工程に送るのって、完全にカンバンだなぁ。日本の製造業のプラクティスがまた世界を変えてしまった(違う。
余談2
今回の発表では、Reactive Programming のような非同期プログラミングについて、私が最近考えている『プログラミングモデルについての議論は概ね決着がついていて、焦点は「いかに高機能なランタイムを提供するか」という所に移りつつあるのではないか』という話を入れてみましたが、さて、実際のところどうなんでしょうか…。みなさんはどう思われますか?
これはずっとそうだと思うのですが、特に昨今、UI プログラミングやマイクロサービスといった文脈で非同期プログラミングの需要が高まる中でも、「非同期を同期的な文法で書きたい」というニーズは非常に根強いものがあります。
しかし、以前に「マイクロサービスが Scala を選ぶ3つの理由」という記事でも書いたように、特に分散システムの文脈では(有名な「分散コンピューティングの落とし穴」が述べるように)、レイテンシや処理の失敗、ネットワークの不安定性、あるいはそれを補うための運用監視といった話題は無視できません。過去に、「同期的な文法で非同期プログラミングができる」というコンセプトを打ち出したプログラミングモデルが大体失敗に終わったのは、そういった事情でしょう。
そう考えると、「同期プログラミングにとっての異物」をプログラマの目から隠してしまうのではなく、少し考え方を変えて明示的に扱った方が、最終的には幸せになれる気がしてきます。そして、Future/Promise や(Rx の)Observable のような関数型インタフェースは、非同期な実行モデルに基づくデータフローを記述する上で、優れた抽象化を提供してくれます。Reactive Programming が、多くの場合で関数型 Reactive Programming (FRP) の同義語として扱われるのは、そうした抽象化がもたらす利便性が大きな理由でしょう。
そんなわけで、本来であれば、近年の Reactive Programming の実践は「関数型プログラミング」と極めて密接な関係があります。しかし、今回は大前提として Java ユーザの方に向けた発表なので、そういった話題は基本的に除外することを心がけました。実際、スライドを見ていただければお分かりになると思いますが、「関数型とは何か」という議論に立ち入らなくても Reactive Programming を理解し活用することは可能です。
一方で、これらのライブラリをより効果的に活用したいと望むなら、関数型の考え方を調べておくと非常に役立ちます。この手の話に関心がある方は、以前このブログで書いた「関数型プログラマのための Rx 入門」シリーズをご覧になってみてください。