プログラミング言語Streemをハックしよう!Matzゼミの全貌をレポート

プログラミング言語Rubyの生みの親である"Matz"こと まつもとゆきひろさん。2016年より株式会社リクルートマーケティングパートナーズ(以下RMP)の技術フェロー・技術顧問として尽力いただいております。

2018年2月14日には、スペシャル企画第3弾として、RMP社内で「Matzゼミ」が開講!

前半:プログラミング言語StreemについてMatzさんの講演
Streemはどのようにして出来たのか?今後の展開は?
後半:ワークショップ
Matzが作っているプログラミング言語 Streemをハックしよう

MatzさんによるStreemの講演は全世界初公開の内容です。この記事であなたも「Matzゼミ」を体験してみましょう!

(取材・文章: 湊川あい)

Streemという言語ができた経緯

まつもと氏

言語デザインの楽しさを伝えたい!」そんな思いから、日経Linuxにて「作りながら学ぶプログラミング言語」という連載を始めました。その中で生まれたのがStreem(ストリーム)という言語です。

この連載、やってみてから分かったんですけど無謀な挑戦で。締め切りは毎月来るし(笑)。
言語デザインについての連載ですから「言語デザインの過程」「どんなことを考えながらデザインするのか」というのを書くわけです。

これって考えないと書けないんですよ。調べても出てこないんですよね。だって言語をデザインする話だから。もちろん「Rubyは過去にこうしました」という話はできるんですけど、そういった話は既にいっぱいしてきているので。

  • 新しい言語を考えて
  • どういう風にデザインをするのか
  • どうやって実装するのか

こういうことを毎月頭をひねりながら書いていました。

新しい言語を開発することの楽しさ

ありがたいことに、プログラミング言語Rubyは2018年2月に生誕25週年を迎えました。世界中のたくさんの人たちが自分の作った言語を使ってくれているというのはとても嬉しいことです。その反面、大変なこともあります。ユーザーが多いぶん、言語を変えられなくなるんですよ。間違いを訂正しにくくなる。

あのときはいいと思って実装したけど、今考えればまずかった。この機能を落とそう」なんてすると、まわりから「ギャッ」と小さな悲鳴が聞こえるんですよね。どんな小さな機能であっても、ユーザーが何百万人にもなると、誰かが使っている。「その機能を落とすとナントカgemが使えなくなって、うちのRailsアプリケーションが動かなくなるからやめてくれ」とか。

会場笑

そういうこともあって、Rubyという言語に対して大胆な新しいことをしようとすると難しいわけです。たとえば静的な型を入れるとかね。

ソフトウェア開発全般の楽しいところっていろいろ試すことだと思うんですよね。ちょっとやってみて「便利になった」とか「これは失敗だった」とか。

新しく作った言語なら、いろいろ試せる。そんな言語デザイン欲がStreem開発の背景なんです。

言語を作ること=思考パターンをデザインすること

まつもと氏

言語を作るということは、思考パターンをデザインするということだと思うんですね。使う言語によってプログラマの思考が方向付けられる。日々Rubyを書いている人たちは、Rubyによって思考が影響されてくる。Ruby脳になっていく。言語デザインとは、ある種思考ガイドをデザインしているということなんです。私はそういうのをやりたいんですよ。

そういうわけで、言語のアイデアをいろいろ考えました。その中から2つを紹介しますね。

Duck

最初に考えたのがDuck(ダック)。その名の通り、ダックタイピング言語です。静的な型を持つんだけど、構造的な型を持つ。全部ダックタイピングなんだけど、Staticな型を持ちます。これは思考実験みたいなもので、実装には至らなかったのですが、Duckのアイデアの一部は将来のRubyに取り込まれる予定です。

Streem

2番目に作ったのがStreemというストリーム処理言語です。200行くらいYaccで文法定義だけ書いて、バックアップのためにGitHubのパブリックリポジトリに上げたんですね。そしたら何もアナウンスしてないのに見つけた人がいて(笑)。Hacker Newsに載ってバズっちゃったんです。200行しかないのにGitHubのスターが1000ぐらいついて……。

開発の過程はこの本にまとまっています。

言語のしくみ

Streem開発の背景

汎用言語はもういいよ

汎用言語というのは、なんでもできる言語という意味です。たとえば、PHPはWebメインですが、Web以外にも使ってもいいですよね。RubyもRailsアプリケーションに使われることが多いけれど、それ以外に使ってもいい。

考えてみれば、汎用性を追求し続けると、結局のところみんな似たようなものになるんですよね。「Pythonがいい」とか「Rubyがいい」とか言いますけど、冷静に考えれば、PythonもRubyも本質的なところではあまり違いません。大概の言語はオブジェクト指向であったり関数型言語です。型によっていろんな派閥があるけど「誰も見たことのないような型」なんて必要レベルではあまり出てきません。今から新たに汎用原語を作っても勝てないし、もし勝てたとしても正直言ってデザインしてて面白くないんですよ。

UNIX時代への懐古

UNIX時代には、用途を限定した小さな言語というのがたくさんありました。たとえばawk、sed、bcなど。bcって一応プログラミングできるんですよね。ループも書けるし。とにかく単目的でわかりやすい。

最近読んで面白かったのが「21世紀のシェルスクリプト」という話。プロセスをパイプラインでつなぐと、それぞれのプロセスがマルチプロセスで動くので、マルチコアを非常に活用できるんですね。「マルチコアを拡張するためには、21世紀こそシェルスクリプトだ!」と言っている人たちがいて。

そういう単目的のツールというのは尖っていて面白いんじゃないかと。

ビッグデータ

近年、自治体や研究所が自分たちで集めたデータを公開するということが増えてきました。政府としても「オープンデータ化を進めましょう」という方向性があります。

こういったデータは、だいたいのものはCSVやJSONで提供されています。さらにこういったデータを処理できるGoogle Sawzall(ソーゼル)やAWS Lambda(ラムダ)が登場しました。

こういった流れの中で、ストリームベースプログラミングというのは可能性があるんじゃないかと。

ストリームしかない言語で、どこまでいけるか見てみたい

Streemを作る上で大事にした観点は次の通りです。

  • 迷ったら「Rubyとは違うデザイン」にする
  • できるだけミニマルにする
  • たとえば、ループがないとか

Streemの特徴は、簡潔で直感的なパイプラインです。難しいことを考えなくてもよきに計らってくれます。

Streemの基本

Streemの基本的な構文がこちらです。

stdin | stdout

Streemの基本説明

  • stdinstdoutをつなげたパイプラインを作る
  • プログラム終了でイベントループへ入る
  • 途中でパイプラインが増えることもある
  • 全部のパイプラインが終わるとプログラムが終了する
  • 要素をひとつずつ吐いて、全部吐き終わったらストリームが終了する

という具合です。

StreemでHello World

Hello Worldという文字列を出力するにはこうですね。

["Hello World"] | stdout

StreemでFizzBuzz

みんな大好きFizzBuzzもできます。

  • 15で割り切れるときはFizzBuzz
  • 3で割り切れるときはFizz
  • 5で割り切れるときはBuzzを返します

これらを最後にstdoutに渡して出力となります。

Streem Echo Server

# simple echo server on port 8007
tcp_server(8007) | each{s->
  s | s
}

ソケットって双方向なので、自分自身につなぐとechoができるんですね。入力したものを出力する。

Streem netcat

s = tcp_socket("localhost", 8007)
stdin | s
s | stdout

stdinから入力したものを、ソケットに送りつける。そしてソケットから送られたものをstdoutに吐く。

Streemパターンマッチ

each2 = {
  case [], f -> []
  case [x, *y], f -> f(x); each2(y,f)
}
each2([1,2,3,4]) {x-> print(x)}

これは簡単なeachですね。空リストだったら空リストを返す。最初の1要素だけ受け取って、その1要素に関数を適用し、残りの要素を再帰的にeach2に渡す。

each2のところを見てもらうと、末尾に関数の引数があります。Rubyのブロックのように記述すると末尾の引数に無名関数として渡されるんですね。これはSwiftとかGroovyでも使われている書き方です。

エラー処理はSawzallを参考にした

まつもと氏

エラー処理はSawzallを真似しました。Sawzallは、無効なデータを見ると、なんと無視しちゃう。スキップしちゃうんですよ。すごく大胆ですよね!?

大量のCSVを読み込ませたりすると「ここの行だけ要素の数が違う」なんてケース、往々にしてありますよね。他にも、エスケープが間違っていて、うしろを全部読み飛ばしてしまったというケースもあります。この場合、普通の言語だと処理が止まって「失敗しました」「タイプが合いませんでした」と返ってくると思うんですけど、Sawzallはそうじゃない。エラーデータがあっても、ただ単に読み飛ばしちゃうんです。

なぜか。Sawzallの場合、とにかく巨大なデータを扱うことを想定しているからです。外部から来た、必ずしも正規化されていないデータたちです。特にWebから読み込んできたデータですね。たとえばHTMLタグが正しくないとか、CSSが壊れているとか。おかしいデータは常に存在している。そういう大量のデータに対して「こんなケースがある、あんなケースがある」と考えるのは現実的じゃないんですね。いずれにしてもドロップするんだったら、最初から無視して人間は考えなくていいという考え方なんです。

問題は開発中……

このやり方を取り入れて、困ったのは開発中ですね。エラーが起きても全部ドロップしてしまう。つまり、自分のプログラムのバグのせいなのか、異常データのせいなのかがわからない(笑)。

会場笑

そこで、開発モードというのを入れました。任意で開発モードに切り替えられるようにし、その間はエラーを吐いてくれるという。ある程度出来上がったら本番モードに切り替えて、異常を含んでいるかもしれないデータを取り込んでテストしました。

Streemの課題

Streemについて今後やりたいなと思っていることをいくつか紹介しますね。

イベントループの改善

以前、ロックフリーキューをやろうとしたらバグがあって。ここをロックフリーに戻したいです。

順序問題

CSVファイルから文字列を一行ずつ読みこんで、アルファベットを全部大文字にしてから出力するとします。このとき、初期のStreemだと行が前後してしまうんです。早いもの順に処理して、処理が終わった行から順に次々吐き出していくからです。ところが、CSVファイルの場合、行の順番って意味がないことのほうが多い。そうすると各行ごとに処理して早いもの順に出した方が早い。

とはいえ「順序を指定する方法がない」というのもよくないので、その部分について考えています。今は同じパイプラインは同じスレッドで動くようにして順序を保証しているんですけど。これって無駄なことをしている状態なんですよね。順序を気にしなければもっと処理速度が早くなる。

文法のオーバーホール

初期のRubyはよかったです(笑)。「今回のバージョンからこの書き方です!」ができる。今のStreemは初期のRubyと同じ状況なので、トップレベルの文法の整理ができます。

関数の省略表記

Scalaみたいな書き方が羨ましいなと思うことがあるので、取り入れてみたいと思っています。

明示的エラー処理

明示的にエラー処理が書けるといいときもあるかな?」と思ってのことです。Streemの良さってパイプラインを繋いでいく書き方ができるところだと思っています。

でもパイプラインって、エラーが起きると分岐するんですよね。その分岐した時のパイプラインをどんな記法で書くか。そうすると、二次元プログラミング言語みたいになりそうで頭が痛いですよね。

JSONのツリー型データ構造の表現方法

今のところJSONのツリー型のデータ構造を直接表現する方法がないんですね。最近JSONを使うケースが増えてきているので、これは結構大事。配列、ハッシュ、値。そういったもので構成されるツリーをどのように表現するか。どう実装するか。それから、クエリをどうするか。ツリーのスキーマを表現できるようにするのかしないのか。表現するならどうやってバリデートするのか。さらに、JSONとツリー型データ構造を相互変換できるようになれば、JSON系のオープンデータを活用できる幅が広がって、Streem活用の幅が広がる可能性があります。

以上、Streemの背景と課題をお話しました。このあと午後のワークショップもあるので、その際に疑問点など自由に質問してもらえればと思います。

ランチタイム

Matzゼミ前半が終わり、みんなでランチタイム。お寿司🍣とハンバーガー🍔が振る舞われました。

ボリューム満点のハンバーガーにMatzさんも嬉しそうです。

Matzゼミ後半

午後からはワークショップの時間。「プログラミング言語 Streemをハックする」というテーマで、RMPのエンジニアたちが、それぞれ自分の取り組みたいことを考えてグループを組みました。

みなさん何をする予定なんでしょうか。作業中のところをのぞいてみましょう!

言語100本ノック班

「Streemで言語処理100本ノックに挑戦」松島さん

言語100本ノックというのは、こちらのサイトに載っているような、言語を使った練習問題の事です。こういうのはRubyは得意だけどStreemでやろうとすると難しい。Streemに手を加えつつ100本ノックをやりながら、疑問に思った点はMatzさんに聞いて理解を深めたいと思っています。

sssssssh班

「コマンドでStreemを実行できるものを作ります」大島さん・相野谷さん

僕たちは開発支援グループのエンジニアです。普段はスタディサプリなどの自社サービスのインフラを担当しています。

これから2時間で、ファイルを作らなくてもコマンドでStreemを実行できるツールを制作しようと思います!Rubyでいうirbみたいなものですね。名前はssssssshと名付けます(笑)。

プログラミング言語作ろう班

「Go言語でStreemを実装します」海沼(かいぬま)さん

Streemは新しい言語で仕様がまだ小さいので、プログラミング言語を作るというテーマの上で選んでみました。Goは並列処理周りが得意なので、Streemと組み合わせたときの可能性があるんじゃないかと思ってのことです。

Streemで遊んでみる班

「まずはキャッチアップ」市川さん、戸井田さん

僕たちは、まずはStreemに触れてみようということで。コードを追いながらキャッチアップしていっています。

型つき言語でのストリーム処理班

「ストリームを関数型言語で使うには?」吉村さん

2週間前にRMPにジョインしました。LaTeXが大好きです!

今回は型つき言語でのストリーム処理について調べて、最後のLTでみんなに紹介しようと思います。

Iterateeというデータ構造がありまして。それを使うとHaskellのような言語でもストリーム処理ができるんですよ。Iterateeの構造をもとに「ストリームってどうやって使うのが便利なんだろう?関数型言語で使うにはどうすればいいんだろう?」ということを深堀りしたいと思います。

Streemビジュアル化班

「Streemの内部の実行の様子を可視化します!」原さん

p5.js1)電子アートとビジュアルデザインのためのライブラリを使って、Streemの内部の様子を見えるようにしたくて。Streemの内部で実行していることをイベントに起こして、必要そうなデータをJSONにしたので、あとはこれをビジュアル化するべくp5.jsでコーディングしていきます!

発表タイム

2時間が経過し、ついに発表タイムがやってきました!みなさんの作ったものを見てみましょう。

sssssssh班

相野谷さん・大野さん:
僕たちはStreemをShellの中で使うっていうのを目標にしました。ビッグデータ時代につかえるShellです!

プロジェクトの名前はssssssshと名付けました。その理由は……ストリームシェルの略だとsshになってしまう。それだと既存のsshとかぶるので、ffftpをオマージュしてこの名前にしました。

実行するとこんな感じです。

ちゃんとStreemをコマンドから直接使えるようになりました!

で、実装ですけど、これはRubyで書かれています。

会場笑

途中行き詰まったところもあったんですけど、結局Streemってstdinとstdoutがないといけない。そこにたどり着くのに時間がかかりました。

まつもとさん:
こういうREPLみたいな機能は嬉しいと思います。どうせならヒストリーをどこかに保存しておいて適用できるといいですよね。

言語100本ノック班

松島さん:
私はStreemで100本ノックをやってみました。
が、これが一番最初の問題から困って、Streem本体に手を入れることでできるようになりました。

最初の問題は00番「文字列"stressed"の文字を逆に並べた文字列(desserts)を得よ」という問題です。Streemにはそもそも文字列を反転させるという機能はないので、どう反転させようかなとまつもとさんに相談しました。

chars作って、array作って、Reverseして。すると「stressed」が反転した配列として渡ってくるので、reduceしてあげればひとつの文字列になる、という具合です。そのためにバッチを2つ書きました(笑)。
多分これでいいんじゃないかなと思うんですが……。

次に100本ノック01番。「パタトクカシーー」という文字列から1、3、5文字目をとってきて「パトカー」にするという問題です。が、3回に1回ぐらい「タトカー」になってしまいます。

まつもとさん:
後ほどレビューします!

会場「おお〜」

松島さん:
charsとreduce処理について深めることができたので、よかったと思います!

Streemで遊んでみる班:Alexaを喋らせてみる

市川さん:
Matzさんのお話で「Streemはソケットを扱える」ということだったので、httpのレスポンスを組んでAlexa 2)Amazon Echo などに搭載されているクラウドベースの音声サービスに喋らせてみました。

Streemのサーバを呼ぶと……

Alexa:
Welcome to Streem.

会場「ええっ!?すごい!喋った!」

市川さん:
HTTPのレスポンスを自分で組んであげて、Alexaが理解してくれるJSONの部分に言葉を入れてあります。

まつもとさん:
AlexaがStreemで喋って感動しました!やっぱりこういうのはデモ映えしますね!

型つき言語でのストリーム処理班

吉村さん:
ストリームを関数型言語で使うには?」というのを調べたので、LTでみんなに紹介します。

Iterateeというライブラリーがあります。これの何がいいかというと、より柔軟なストリーム処理ができることです。コールバックを全部処理したあとに、何か処理をして返すということができるんです。行きも帰りも両方できるからより進んだストリーム処理ができるんじゃないかと考えています。

相野谷さん:
タイムアウトとかで落ちちゃったとき、リミットをもうけてkillしておく、みたいなところに使えそうだなって思いました!

まつもとさん:
うしろと話をしたいときって
・エラーが出てきた場合
・うしろが時間がかかる処理の場合
だと思うんですね。
それで、今回のお話はエラー時の対応に使えるということですよね。大変興味深いと思います。

プログラミング言語作ろう班:Go言語でStreemを再実装

海沼さん:
Go言語でStreemの四則演算を実装しました。Matzさんの書籍にYaccというのがあったので、それを参考に作成しました。ルールを書くと実行してくれるというものですね。もっと機能を追加したいんですが、今日のところはここまでという形になりました。

まつもとさん:
言語作る機会って、仕事の中ではあまりないと思いますが、楽しいしスキル向上にもつながるので、ぜひこれからも作っていってください!

Streemビジュアル化

原さん:
私は「Streem内部の実行の様子を可視化する」ということをやりました。

まず最初に、Streemの内部で実行していることをイベントに起こして必要そうなデータをJSONにしました。このデータをJavaScriptに入れて、p5.jsで可視化してみました。

FizzBuzzのプログラムを可視化するとこんな感じになりました。スレッドがポンポンとできて、タスクができて、消化されて……と動いていきます。

会場「おお〜!おもしろい!」

まつもとさん:
この短い時間で当初のテーマを実装し、見栄えもよくて、素晴らしいと思います!あとは、どこにどんな値が入っているかも表示されるとよりおもしろくなりそうですね。

金谷さん:
これ音楽にしましょうよ!Streem music!

"Matz"との距離が近いリクルートマーケティングパートナーズ

イベントの後は、突如、書籍まつもとゆきひろ 言語のしくみのサイン会も発生!
まつもとさんのレアなサインに、みなさん大興奮。

Matzさんのサインを待つエンジニアたち

Matzさんのサイン

そのあとは、まつもとさんとRMP社員でわいわい談笑。なごやかな雰囲気の中、まつもとさんを中心にプログラミング談義が繰り広げられていました。

"Matz"との距離が近いリクルートマーケティングパートナーズ。このようにまつもとさんと直接話し合える場は、大変なモチベーションになりますね。

株式会社リクルートマーケティングパートナーズでは一緒に働く仲間を募集しています

リクルートマーケティングパートナーズではエンジニアを募集しております。一緒に働きたいと思った方はこちらから!

RMPが開発しているプロダクト

脚注

脚注
1 電子アートとビジュアルデザインのためのライブラリ
2 Amazon Echo などに搭載されているクラウドベースの音声サービス