ブロックプログラミング言語Treesであそんでみた
先日、Qiitaにてこのような記事がでました。
すごく書いてみたいと思ったので今日はTreesに挑戦した話を記事にしました。
そうだ、メソッドチェーンもどきを書いてみよう
私の所属しているコミュニティには、このようなネタOSSが存在しています。
言語機能を活かして「前前前世」を出力しよう、というものです。
Treesでなにか書きたいな、と思っていたのでこのRADWIMPSに挑戦しようと思った次第です。
つまりこういうことをしました
┌─────┐
│ seq ├───────────────────────────────────────┐
│ ├──────────────────────────┐ │
└─┬───┘ │ ┌─┴──┐
┌─┴─────┐ │ │then│
│defproc├─────┐ │ └─┬──┘
└┬──────┘ ┌─•───┐ │ ┌─•──┐
┌┴─────┐ │ seq ├──────┐ │ │then│
│"then"│ └─┬───┘ ┌┴───┐ │ └─┬──┘
└──────┘ ┌─┴───┐ │exec│ │ ┌─•──┐
│print│ └┬───┘ │ │then│
└─┬───┘ ┌┴─┐ │ └─┬──┘
┌─┴────┐ │$0│ │ ┌─•──┐
│"then"│ └──┘ │ │ se │
└──────┘ │ └────┘
┌──────────────────────────────┘
┌─┴─────┐
│defproc├─────┐
└┬──────┘ ┌─•───┐
┌┴─────┐ │ seq │
│"se" │ └─┬───┘
└──────┘ ┌─┴───┐
│print│
└─┬───┘
┌─┴────┐
│ "se" │
└──────┘
Treesとは
Trees は、ブロックプログラミング言語で、以下の特徴を備えています!
分かりやすい (easy to understand)
読みやすい (readable)
曖昧性がない (clear)
(公式README)より
というわけで、かなり読みやすい言語です
書くのはs
実行手順
Repoをクローン
インタプリタをビルド
cargo build --release
お好みのファイルを実行
<path_to_Trees>/target/release/trees <trees_file>.tr
基本的な構文
詳細はWikiに掲載される予定らしいので、今回使ったところを紹介します
主観もちょっと入ってるかもしれないです
ブロック
罫線を用いたブロックが基本単位になります。
このブロックは関数として扱われます。
例えば、定数3を表す場合、
┌───┐
│ 3 │
└───┘
のように書きます。これは、
() => 3
とみなすことができます。
プラグ
ブロック同士をつなぐ結線の接合のことで、Treesはこのプラグをもちいて実行を制御できます。
プラグはブロックプラグと引数プラグに分類ができ、ブロック上部のただ一つのプラグをブロックプラグ、それ以外の辺から生えるプラグを引数プラグと呼びます。
これらのプラグは交差してはいけません。
また制約として、単一のプログラムにおいて
- ブロックプラグを持たないブロックがただ一つ存在すること
- 他のブロックは必ずブロックプラグを持つこと
この2つが存在します。ブロックプラグを持たないブロックが、プログラムのエントリポイントとなり実行開始に最初に評価されるブロックになります。
seq
順次実行 引数プラグは反時計回りに順番が定義され、seq
(sequence)は引数プラグに渡されたブロックをその順番で実行します。
手続き的な書き方をしたい場合、このseq
は必須でしょう。
defproc
プロシージャ定義 もちろんTreesには標準で用意されているブロックキーワードがいくつか存在しており、自作関数を作成するキーワードがdefproc
(define procedure)です。
┌───┐
│seq├────────────────────┐
└─┬─┘ ┌┴────┐
┌─┴─────┐ │print│
│defproc├─────┐ └┬────┘
└┬──────┘ ┌•┐ ┌┴────────┐
┌┴──────────┐│*├─┐ │two times│
│"two times"│└┬┘┌┴─┐ └┬────────┘
└───────────┘┌┴┐│$0│ ┌┴┐
│2│└──┘ │3│
└─┘ └─┘
引用: Wiki
先程までのプラグとseq
の解説も同時にすると、
- ブロックプラグ(上部の結線)を持たないブロック(最上部の
seq
ブロック)が存在するので、そこから開始される -
seq
ブロックは反時計回りに引数プラグ先のブロックを実行するので、下部のdefproc
が実行される -
defproc
は第0引数に関数名、第1引数にプロシージャが実行するブロックを受け取る -
defproc
処理が終わったので、右に伸びる引数プラグ先のprint
ブロックに移る -
print
ブロックは引数プラグ先のブロックを実行し、評価された値を出力する -
two times
ブロックは3を第0引数に受取、2倍する - 結果、6が評価される
-
print
ブロックにわたり、「6」が出力された!
ざっとこのような処理になるでしょうか
ちょっと見慣れない記法が出てきましたね(そもそも全部見慣れない)
defproc
に渡しているプラグに•
が存在しています。これが今回の記事で目玉のブロッククォート機能です。
ブロッククォート
最初のブロックの紹介でもお話したように、ブロックは関数として扱われています。そして、この•
は、ブロックを遅延評価する機能を持っています。つまり実行時まで下部含めたブロックが実行されない、ということです。
まさにdefproc
はこの機能を利用しており、関数本体の記述としてブロック群をそのまま(評価させずに)渡すためにブロッククォートを使っているのです。
ちなみに、ブロッククォートを使わない場合実行時に評価されてしまいvoidが返るようになってしまうためエラーが発生します。
最初のコードの解説
さて、基本的な構文を抑えたうえで再度本題のコードを見ていきましょう。
┌─────┐
│ seq ├───────────────────────────────────────┐
│ ├──────────────────────────┐ │
└─┬───┘ │ ┌─┴──┐
┌─┴─────┐ │ │then│
│defproc├─────┐ │ └─┬──┘
└┬──────┘ ┌─•───┐ │ ┌─•──┐
┌┴─────┐ │ seq ├──────┐ │ │then│
│"then"│ └─┬───┘ ┌┴───┐ │ └─┬──┘
└──────┘ ┌─┴───┐ │exec│ │ ┌─•──┐
│print│ └┬───┘ │ │then│
└─┬───┘ ┌┴─┐ │ └─┬──┘
┌─┴────┐ │$0│ │ ┌─•──┐
│"then"│ └──┘ │ │ se │
└──────┘ │ └────┘
┌──────────────────────────────┘
┌─┴─────┐
│defproc├─────┐
└┬──────┘ ┌─•───┐
┌┴─────┐ │ seq │
│"se" │ └─┬───┘
└──────┘ ┌─┴───┐
│print│
└─┬───┘
┌─┴────┐
│ "se" │
└──────┘
正直なところ、見るだけで何がしたいかがぱっとわかってしまうのがこの言語の恐ろしいところです。
(exec
は引数のブロックを実行するものです。)
なのでもう解説しなくていいですか?と思うんですが、解説します
本体
┌─┴──┐
│then│
└─┬──┘
┌─•──┐
│then│
└─┬──┘
┌─•──┐
│then│
└─┬──┘
┌─•──┐
│ se │
└────┘
シンプルですね。他の言語のように書くとすれば
then().then().then().se()
のようになります。
ブロッククォートを使っているのは、then
の実装によるものです。
then
┌─┴─────┐
│defproc├─────┐
└┬──────┘ ┌─•───┐
┌┴─────┐ │ seq ├──────┐
│"then"│ └─┬───┘ ┌┴───┐
└──────┘ ┌─┴───┐ │exec│
│print│ └┬───┘
└─┬───┘ ┌┴─┐
┌─┴────┐ │$0│
│"then"│ └──┘
└──────┘
"then"を出力したあとに、$0
のブロックを実行します。
つまり、本体でthenのあとにブロッククォート付きで渡された関数が実行される、ということです。
se
┌─┴─────┐
│defproc├─────┐
└┬──────┘ ┌─•───┐
┌┴─────┐ │ seq │
│"se" │ └─┬───┘
└──────┘ ┌─┴───┐
│print│
└─┬───┘
┌─┴────┐
│ "se" │
└──────┘
"se"を出力します。
実行結果
本来のレギュレーションは日本語出力なので、少しブロックの体裁が崩れてしまいますが、このように実行されます。
まとめ
罫線を入れるのがめちゃくちゃしんどいです。
Discussion