🌳

ブロックプログラミング言語Treesであそんでみた

2024/03/29に公開

先日、Qiitaにてこのような記事がでました。
https://qiita.com/Snowman-s/items/252ddf9f4327eec8413e

すごく書いてみたいと思ったので今日はTreesに挑戦した話を記事にしました。

そうだ、メソッドチェーンもどきを書いてみよう

私の所属しているコミュニティには、このようなネタOSSが存在しています。

https://github.com/approvers/RADWIMPS

言語機能を活かして「前前前世」を出力しよう、というものです。
Treesでなにか書きたいな、と思っていたのでこのRADWIMPSに挑戦しようと思った次第です。

つまりこういうことをしました

RADWIMPS.tr
┌─────┐      
│ 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 │
└───┘

のように書きます。これは、

() => 3

とみなすことができます。

プラグ

ブロック同士をつなぐ結線の接合のことで、Treesはこのプラグをもちいて実行を制御できます。
プラグはブロックプラグと引数プラグに分類ができ、ブロック上部のただ一つのプラグをブロックプラグ、それ以外の辺から生えるプラグを引数プラグと呼びます。

これらのプラグは交差してはいけません。

また制約として、単一のプログラムにおいて

  • ブロックプラグを持たないブロックがただ一つ存在すること
  • 他のブロックは必ずブロックプラグを持つこと

この2つが存在します。ブロックプラグを持たないブロックが、プログラムのエントリポイントとなり実行開始に最初に評価されるブロックになります。

順次実行 seq

引数プラグは反時計回りに順番が定義され、seq (sequence)は引数プラグに渡されたブロックをその順番で実行します。
手続き的な書き方をしたい場合、このseqは必須でしょう。

プロシージャ定義 defproc

もちろんTreesには標準で用意されているブロックキーワードがいくつか存在しており、自作関数を作成するキーワードがdefproc (define procedure)です。

sample_proc.tr
┌───┐      
│seq├────────────────────┐
└─┬─┘                   ┌┴────┐
┌─┴─────┐               │print│
│defproc├─────┐         └┬────┘
└┬──────┘    ┌•┐        ┌┴────────┐
┌┴──────────┐│*├─┐      │two times│
│"two times"│└┬┘┌┴─┐    └┬────────┘
└───────────┘┌┴┐│$0│    ┌┴┐
             │2│└──┘    │3│
             └─┘        └─┘

引用: Wiki
先程までのプラグとseqの解説も同時にすると、

  1. ブロックプラグ(上部の結線)を持たないブロック(最上部のseqブロック)が存在するので、そこから開始される
  2. seqブロックは反時計回りに引数プラグ先のブロックを実行するので、下部のdefprocが実行される
  3. defprocは第0引数に関数名、第1引数にプロシージャが実行するブロックを受け取る
  4. defproc処理が終わったので、右に伸びる引数プラグ先のprintブロックに移る
  5. printブロックは引数プラグ先のブロックを実行し、評価された値を出力する
  6. two timesブロックは3を第0引数に受取、2倍する
  7. 結果、6が評価される
  8. printブロックにわたり、「6」が出力された!

ざっとこのような処理になるでしょうか
ちょっと見慣れない記法が出てきましたね(そもそも全部見慣れない)
defprocに渡しているプラグにが存在しています。これが今回の記事で目玉のブロッククォート機能です。

ブロッククォート

最初のブロックの紹介でもお話したように、ブロックは関数として扱われています。そして、この は、ブロックを遅延評価する機能を持っています。つまり実行時まで下部含めたブロックが実行されない、ということです。
まさにdefprocはこの機能を利用しており、関数本体の記述としてブロック群をそのまま(評価させずに)渡すためにブロッククォートを使っているのです。

ちなみに、ブロッククォートを使わない場合実行時に評価されてしまいvoidが返るようになってしまうためエラーが発生します。

最初のコードの解説

さて、基本的な構文を抑えたうえで再度本題のコードを見ていきましょう。

RADWIMPS.tr
┌─────┐      
│ 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

then()
┌─┴─────┐
│defproc├─────┐
└┬──────┘   ┌─•───┐
┌┴─────┐    │ seq ├──────┐
│"then"│    └─┬───┘     ┌┴───┐
└──────┘    ┌─┴───┐     │exec│
            │print│     └┬───┘
            └─┬───┘     ┌┴─┐
            ┌─┴────┐    │$0│
            │"then"│    └──┘
            └──────┘

"then"を出力したあとに、$0のブロックを実行します。
つまり、本体でthenのあとにブロッククォート付きで渡された関数が実行される、ということです。

se

se()
┌─┴─────┐ 
│defproc├─────┐
└┬──────┘   ┌─•───┐
┌┴─────┐    │ seq │
│"se"  │    └─┬───┘
└──────┘    ┌─┴───┐
            │print│
            └─┬───┘ 
            ┌─┴────┐
            │ "se" │
            └──────┘

"se"を出力します。

実行結果

本来のレギュレーションは日本語出力なので、少しブロックの体裁が崩れてしまいますが、このように実行されます。

まとめ

罫線を入れるのがめちゃくちゃしんどいです。

Discussion