コンパイラ
パーサ
antlr
プログラミング言語

仕事がうまくいかないのでプログラミング言語を作った

私事で恐縮なのですが、最近いろいろなことが全く上手くいっておりません。お金がないので冷凍うどんばかりを食べています。かつてポール・グレアムはスタートアップを続けていける最小の収入を表す「ラーメン・プロフィッタブル」という言葉を作りましたが、私なら「うどん・プロフィッタブル」と名付けますね。イオンの冷凍うどんは5食で200円ですよ! とにもかくにも、あまりに仕事が上手くいかないので、気晴らしにプログラミング言語を作りました。

hello, world

trigger Once |> \ -> "hello, world" |> Stdout

どんな言語なのか

イベント駆動・パイプライン志向の言語です。適当にpiperと名付けた。PiedPiper社の支援を受けたりはしてない。

イベント駆動というのは、プログラムが実行されるイベントトリガーを明示的に指定する点を指します。多くのプログラミング言語ではmainメソッドを起動するという形をとりますが、この言語ではファイル中に「〇〇イベントが発生した場合にプログラムを実行しなさい」というトリガーを指定します。
上記HelloWorldではOnceトリガーを指定しています。これは「起動時に一度だけプログラムを実行しなさい」という意味です(実質的main)。ほかにもHTTPリクエストを受け取った場合、ある決まった時間で、などが考えられます。
この仕組みの直接的な発想は、AWSのLambdaのようなサーバーレスアーキテクチャーを、プログラミング言語自体に適応できないかと思ったことです。

パイプライン志向というのは、データ処理を『関数のパイプライン』を通して変形させていくことを指します。パイプラインはRxシリーズで皆様おなじみではないでしょうか。古くはLinuxのパイプもそうですし、まつもとゆきひろ氏が開発したStreemなどもこうした発想の延長線上にあるかと思います。

最後にアウトプットを指定します。Stdoutは標準出力です。他にもファイルやHTTPなどが考えられます。

起動

java --version  # 1.8+ required
git clone https://bitbucket.org/minebreaker_tf/piper.git
cd piper
./gradlew run --args "sample/helloworld.piper"

時報

trigger Timer interval=1000
    |> \ -> "現在時刻は" + now() + "です"
    |> Stdout

1000msごとに現在時刻を標準出力します。

現在時刻は2018-07-30T15:19:19.715です
現在時刻は2018-07-30T15:19:20.762です
現在時刻は2018-07-30T15:19:21.762です
現在時刻は2018-07-30T15:19:22.762です

時報2

trigger Schedule
        pattern="*/1 * * * *"
    |> \ -> "現在時刻は" + now() + "です"
    |> Stdout

CRONパターン版です。

Webサーバー

trigger Http port=8080 method="post" url="/"
    |> \ request -> getValue(request, "body")
    |> parseJson
    |> getUserName
    |> greet
    |> Http

getUserName: \ params ->
    getValue(params, "username")

greet: \ userName ->
    "ようこそ、" + (if userName then userName else "world") + "さん!"

$ curl http://localhost:8080 -X POST -d "{\"username\":\"John\"}"
ようこそ、Johnさん!

$ curl http://localhost:8080 -X POST
ようこそ、worldさん!

改善案

こんなクソ言語何の役に立つのかって?おもちゃの言語とはいえ、意外とまともに動くと思われたのではないでしょうか。とはいえ所詮おもちゃなので実用には堪えません。以下、こんな改善ができるかな、という妄想をしてみます(あくまで妄想なので悪しからず)。

名前空間

名前空間のない実用的な言語は存在しないと思います。超大事。

namespace hoge.piyo

trigger Once -> (...)

複数のトリガー

現状トリガーは一つしか設定できません。これでは使い物になりませんから、複数のトリガーを設定可能にする必要があります。起動時にトリガーを引数で指定する形でしょうか。

アウトプットをトリガーに

パイプラインの終端をそのままトリガーとして定義することができれば、処理の幅が大きく広がります。

trigger Once |> \ -> (...) |> Trigger name=UserDefinedTrigger
trigger UserDefinedTrigger |> Stdout
trigger UserDefinedTrigger |> File name="log"

パイプラインを分岐して複数の処理を行ったり、並行して処理したりできるようになります。

非同期実行

現在、パイプラインの処理はイベントループのなかで同期的に実行されています。これは実装がらくちんだからという理由でそのようになっているのですが、実用的な言語であれば、各パイプラインは別々のスレッド/プロセス/昨今流行りのなんとかルーチンで隔離して実行されるべきでしょう。そうすれば処理が自然な形で並列化できます。さらに言えばパイプライン内の各関数が独立したスレッドで実行されるようにすれば、処理上のボトルネックとなる個所を自動で特定してワーカースレッドを増やしたりできるようになります。ちょっとしたメッセージキューのような形でトリガーを設計できても面白いですね。

……などといろいろ書きましたが、これLinuxのアイデアのパクリですね。Linuxは凄い。

コメント

完全に忘れていたのですがソースにコメントする文法を用意していませんでした。

FAQ

タイトルを見て、通常のプログラミング言語では扱えない特殊な問題を、特別なDSLを設計して解決する……みたいな記事を想像していたのに、くだらない記事で失望しています。仕事云々は関係ありますか?

クリックベイト

Ah, clickbait. Where would the internet be without it? The answer will shock you!
- Steve Yegge

実用性は?

ない。

有益なものは全て醜い。何らかの欲求の現れだからだ。
- ティオフル・ゴーティエ

もうすでに〇〇という同じコンセプトでもっと優れた言語があります

教えてくれてありがとうございます。

俺の作った〇〇言語のほうが優れてる

凄いですね。FogCreekに応募してみるのはどうでしょうか。

バグがあります

俺は悪くねぇ!

つまんねー記事を書くな

ごめんね。

クラスは作れないんですか?

作れません。

flatMapがないです

今気づきました。確かに必要ですね。

ステートメントの終了はどうやって判断しているのですか?

プログラミング言語におけるステートメントの終了の定義は私の知る限り3種類あって、

  • 明示的な終了トークンを持つ(C、Java)
  • 改行を終了とみなす。\などのトークンを行継続として扱う(Pythonとか)
  • ASI、自動セミコロンインサーションで、文の終わりっぽいところに行末トークンを挿入する (JavaScript、Kotlin)

があります。この言語は改行を終了トークンとみなしているのですが、次の行がスペースから始まっている場合、前の行の継続とみなすという規則になっています。

まとめ

しっかりした言語を作るのはとても大変ですが、こういうお遊びの言語なら簡単に作れます。皆さんも仕事や人生が辛いときは、プログラミング言語を作って現実逃避してみるのはいかがでしょうか? 仕事は進みませんが楽しいですよ。