Zimbu言語を触ってみる

# はじめに

Zimbuは、Vimの作者Bram Moolenaarさんが開発している(していた？)プログラミング言語です。
「開きカッコが無い」、「キーワードが大文字」等 **構文の見た目がかなり個性的です**。

http://www.zimbu.org/

```zimbu:hello.zu
FUNC Main() int
  IO.write("Hello, world!\n")
  RETURN 0
}
```

**見た目以外にも面白い機能が色々あった**ので、チュートリアル片手に触ってみた感想を軽く紹介します。

（超初心者なので、記述に間違いがありましたらコメントいただけるとありがたいです！）

# 特徴

- コンパイラ
- 静的型付け（一部型推論あり）
- クラスベースOOP
- 読みやすさと後方互換を重視

[Javaの影響を強く受けている](http://www.zimbu.org/design/inspiration)ようです。

2010年代には活発に開発されていたようですが、今はリポジトリ（bitbucket）がリンク切れになってしまっていました。

# 環境準備

本家からダウンロードできないので、代わりにコミュニティで紹介されていたミラーと思しきリポジトリを利用します。

https://groups.google.com/g/zimbu-discuss/c/QnhBZ5ZekVc

https://github.com/bsed/zimbuw32

:warning: **本当に本家と同じコードかどうかは分からないので自己責任で**！（試した感じ大丈夫そうでしたが）

Zimbuバイナリのビルドは面倒なので、Dockerイメージを作成しました。良ければご利用ください。

https://github.com/Syuparn/zimbu-image

# hello world

`zimbu` コマンドでコンパイルし、実行ファイルが生成されます。

```zimbu:hello.zu
FUNC Main() int
  IO.write("Hello, world!\n")
  RETURN 0
}
```

```bash
$ docker run -v $(pwd):/work/zimbu -it ghcr.io/syuparn/zimbu:0.1.0 bash
root@5581d8ea87b6:/work/zimbu# zimbu hello.zu
root@5581d8ea87b6:/work/zimbu# ./hello
Hello, world!
```

`ZUDIR` ディレクトリには、中間生成物のCのソースコードがありました。

```bash
root@5581d8ea87b6:/work/zimbu# ls ZUDIR/
hello.c  hello.eca
```

コンパイル時の注意点として、**ルート直下やルート配下のディレクトリでは失敗します**（原因は謎）。

```
# ディレクトリ1つしか掘ってない
root@4326eccc4e3e:/work# zimbu hello.zu
Config.zu line 105 col 7: Exception: using startsWith() with NIL argument
Stack backtrace (last called first):
Config.zu line 105 col 7: Config.run()
zimbu.zu line 86 col 3: Main()
```

# 構文

まずは目を引く構文についてです。
リファレンスには、**文法だけでなくその採用理由や他の候補との比較が載っていました**。単なる暗記より目的が分かったほうが親近感が湧くのでありがたいです。

http://www.zimbu.org/design/keywords-and-names

## 予約語を大文字にする

**後方互換性を保つのが狙い**でした。

アップデートで予約語が追加されると、同じ名前の変数を利用していた既存コードは動かなくなってしまいます。

> For example, one of my Python scripts broke when Python 2.6 was introduced.  The script was written in such a way that it was backwards compatible all the way back to Python 1.5, and now it failed.  It turned out that "as" was used as a variable name, and since Python 2.6 it's a keyword.  This required every user of this script to update to a new version.
> 
> 例をあげると、Python2.6が出たとき私のPythonスクリプトの一部は壊れてしまいました。そのスクリプトはPython1.5までの後方互換を保つよう書かれていたのですが、今は動きません。 `as` を変数名として使っていましたが、Python2.6からはそれがキーワードになったからです。この変更のせいでスクリプトの利用者は新バージョンへのアップデートを余儀なくされました。

http://www.zimbu.org/design/keywords-and-names

そこで、Zimbuでは将来に渡っても予約語と変数名が衝突しないよう

- キーワードは全て大文字
- ユーザー定義変数は全部大文字にはできない

という命名規則を定めています。他にも、

- 組み込み型は全て小文字
- ユーザー定義型は先頭が大文字
- 組み込みモジュールはすべて大文字
- ユーザー定義モジュールは小文字を含む必要がある
- 事前に定義されたメソッド（関数も？）は大文字始まり
- ユーザー定義メソッドは小文字始まり

という規則で、ユーザーと言語自体の識別子を分離しています。

あらためてHello worldを見ると、確かにこの規則に従っていることが分かります。

```zimbu:hello.zu
# FUNC は予約語、Main は事前に定義された関数、intは組み込み型
FUNC Main() int
  # IOは組み込みモジュール
  IO.write("Hello, world!\n")
  # RETURNは予約語
  RETURN 0
}
```

## 開きカッコを省略する

主に以下の2つが導入理由でした。

- 予約語でブロックの始まりが分かるので、開きカッコは不要
- コーディングスタイルの（無駄な）議論を減らしたい

http://www.zimbu.org/getting-started/hello-worlkd

http://www.zimbu.org/design/blocks

既存のブロックの表現がどれもしっくりこなかったのでこの方式にしたようです。

- `{}` (C方式): 開きかっこを改行するかしないかでコーディングスタイルが分かれる
- `BEGIN ... END` (Pascal方式): 文字が多くて読みづらい
- インデント (Python方式): かっこは不要だが、コピペしたときインデントのずれでネストを間違えがち

所見では面食らいますが、確かにエディタの補完もシンタックスハイライトも効くので問題なさそうです[^editor]。

## コーディングスタイルの統一

言語が認めたコーディングスタイル以外は **そもそもコンパイルが通りません** 。
コーディングスタイルの議論を避けるために、文法レベルで公式が決めてしまっています（Goみたいですね）。

```zimbu:コーディングスタイル違反は...
# メソッド名とかっこの間に空白が入っている
IO.write ("Hello, world!\n")
```

```bash:コンパイルできない！
$ zimbu hello.zu
hello.zu line 2 col 11: ERROR: superfluous white space
```

# オブジェクトシステム

オブジェクト指向でうっかりバグを入れてしまったり、冗長になってしまうところを解決する仕組みが導入されていました。

## メンバ変数

メンバ変数は `$var` の形式で書きます。

- `var` だとメンバ変数なのかメソッドの引数なのか分からない
- `this.var` といちいち書くのは面倒

のが理由です。同様に、メソッドの頭にも `$` が必要です。

```zimbu
CLASS Person
  string $name

  # コンストラクタ
  NEW(string name)
    # 引数とメンバ変数が区別できる！
    $name = name
  }

  # NOTE: 戻り値が無い場合、FUNCではなくPROCを使用する
  PROC $greet()
    IO.write("I am " .. $name .. "!\n")
  }
}

FUNC Main() int
  Person person = NEW("Taro")
  person.greet() # I am Taro!

  RETURN 0
}
```

メソッドから別のメソッドを呼ぶことも可能です。

```zimbu
CLASS Person
  # ...
  PROC $hello(Person other)
    IO.write("Hello, " .. other.name .. "!\n")
    $greet()
  }
}
```

## クラス変数、クラスメソッド

`SHARED` ブロックの中に記述します。インスタンスに紐づかないので `$` は不要です。 

```zimbu
CLASS Person
  string $name
  NEW(string name)
    $name = name
    num_people++
  }

  # クラス変数、クラスメソッドの定義
  SHARED
    int num_people = 0
    FUNC num_instances() int
      RETURN num_people
    }
  }
}

FUNC Main() int
  Person person = NEW("Taro")
  Person person2 = NEW("Jiro")
  Person person3 = NEW("Saburo")
  IO.write(Person.num_instances() .. "\n") # 3

  RETURN 0
}
```

staticメソッドとインスタンスメソッドが混ざると「何がインスタンスから触れるか」が分かりづらいので、名前空間を分ける目的で `SHARED` が導入されました。また、クラス自体をオブジェクトにするのはオブジェクトシステムが複雑化するので避けたようです。

http://www.zimbu.org/design/classes-and-modules

とはいえ、たもとを分かった

- `static` キーワードを使うJava
- クラスをClassクラスのインスタンスとして扱うRuby

と結果的に似た構文になったのは興味深いです。文法の収斂進化でしょうか。

## 継承とオーバーライド

親クラスに `@default` がついている場合のみ許可されます。メソッドの予期せぬ上書きを防げて安全ですね。

http://www.zimbu.org/Home/inheritance-example

```zimbu
CLASS Person
  string $name
  NEW(string name)
    $name = name
  }
  PROC $greet() @default
    IO.write("I am " .. $name .. "!\n")
  }
}

CLASS JapanesePerson EXTENDS Person
  PROC $greet() @replace
    IO.write("こんにちは。" .. $name .. "です！\n")
  }
}

FUNC Main() int
  JapanesePerson person = NEW("Taro")
  person.greet() # I am Taro!

  RETURN 0
}
```

メソッド衝突回避のためには、子クラス側にも `@replace` を明示する必要があったようです[^replace]。

> This avoids trouble when a method is added to the base class which was already present in a subclass.  The Zimbu compiler will give an error.  It would go unnoticed in C++ and Java.
> この属性によって、子クラスにすでに存在するメソッドが親クラスに追加された際のトラブルを避けることができます。Zimbuコンパイラはエラーを吐きます。この問題はC++やJavaでは検出されないでしょう。

## コンストラクタ

オーバーロードと組み合わせると、デフォルト値の設定がシンプルに書けます。

```zimbu
CLASS Person
  string $name

  NEW()
    # デフォルト値を指定し、別のコンストラクタに委譲
    NEW("Anonimous")
  }

  NEW(string name)
    $name = name
  }
}

FUNC Main() int
  # 名前指定
  Person person = NEW("Taro")
  IO.write(person.name .. "\n") # "Taro"

  # デフォルト値
  Person person2 = NEW()
  IO.write(person2.name .. "\n") # "Anonimous"

  RETURN 0
}
```

別のコンストラクタに委譲できるのは、以下の規則があるためです。

- `NEW()` がクラスの外のスコープで呼ばれた場合、インスタンスを新規作成
  - メンバ変数はすべてゼロ値
- `NEW()` がクラスの中のスコープで呼ばれた場合、`THIS`を引き続き使う
  - 途中までやった初期化の続きを別の `NEW()` へ委譲可能

http://www.zimbu.org/design/classes-and-interfaces/class-details

## クラスの合成

`INCLUDE` を使い、複数クラスで横断的に使用するメソッド群を合成することができます。

```zimbu
# インクルードする用のクラス片
# `next() int` と `has_next() bool` が定義されている必要がある
PIECE Iterable
  FUNC $index(int target) int
    int cnt = 0
    WHILE $has_next()
      IF $next() == target
        RETURN cnt
      }
      cnt++
    }

    RETURN -1
  }

  FUNC $length() int
    int cnt = 0
    WHILE $has_next()
      $next()
      cnt++
    }

    RETURN cnt
  }
}

CLASS IntList
  list<int> $nums
  int $idx

  # 合成
  INCLUDE
    # 合成したいクラスはメンバ変数として持たせる
    Iterable $iterable
  }

  NEW(list<int> nums)
    $nums = nums
    $idx = 0
  }

  FUNC $has_next() bool
    RETURN $idx < $nums.size()
  }

  FUNC $next() int
    int n = $nums[$idx]
    $idx++
    RETURN n
  }
}

FUNC Main() int
  # includeしたメソッドをインスタンスメソッドとして使える
  IntList nums = NEW([1, 4, 2, 8])
  IO.write(nums.length() .. "\n") # 4

  IntList nums2 = NEW([1, 4, 2, 8])
  IO.write(nums2.index(8) .. "\n") # 3

  RETURN 0
}
```

`INCLUDE` の利点は、 `EXTENDS` と違い継承関係と関係なく振る舞いを共通化できる点です[^include]。Rubyのmix-inのような感覚で使えます。

```zimbu:振る舞いが同じなら、赤の他人のクラスでもロジックを再利用できる！
# next(), has_next()を持っていさえいればIterableが使える
CLASS Range
  int $start
  int $stop
  int $current

  INCLUDE
    Iterable $iterable
  }

  NEW(int start, int stop)
    $start = start
    $stop = stop
    $current = start
  }

  FUNC $has_next() bool
    RETURN $current < $stop
  }

  FUNC $next() int
    $current++
    RETURN $current
  }
}

FUNC Main() int
  Range range = NEW(3, 10)
  IO.write(range.length() .. "\n") # 7

  RETURN 0
}
```

さらに、`PIECE` を使ってメソッドをラップできるようにする計画もあるようです（...が、残念ながら未実装でした）。

http://www.zimbu.org/design/classes-and-interfaces/class-composition

# その他言語の機能
## ジェネリクス

（上の例でもちらっと出てきましたが）`list`, `dict` 等のコレクションで指定します。ユーザー定義クラスにも型パラメータを付けられます。

## 文字列結合

`+` ではなく `..` を使用します。数字を足した時の暗黙の型変換がやっかいなので区別したようです。

http://www.zimbu.org/design/expressions

```zimbu
"4" .. 2 # "42"
```

## bits

値型の構造体を定義できます（※クラスは参照型です）。ビット配列を**1bit単位で定義できる**のでメモリに優しいですね。

http://www.zimbu.org/design/basic-value-types


```zimbu
BITS WriteFlags
  bool  $echo       # 1bit
  bool  $flush      # 1bit
  Where $where      # 2bit (Whereを表せる最小bit数)
  nat5  $repeat     # 5bit (0~31の整数)
}

ENUM Where
  atstart
  halfway
  atend
}

PROC write(string msg, WriteFlags flags)
  IF flags.echo
    FOR i IN 1 TO flags.repeat
      IO.write(msg)
    }
    IO.write("\n")
  }

  SWITCH flags.where
    CASE Where.atstart
      IO.write("[*     ]\n")
    CASE Where.halfway
      IO.write("[***   ]\n")
    CASE Where.atend
      IO.write("[******]\n")
  }
}

FUNC Main() int
  write("Hello", echo + where=atstart + repeat=10)
  RETURN 0
}
```

初期化の形式 `echo + where=atstart + repeat=10` がユニークですが、意味としては

```zimbu
WriteFlags fl
fl.echo = TRUE
fl.where = Where.atstart
fl.repeat = 10
```

と同じです。

`NEW()` を使わなかったのは、参照が返るように見えて紛らわしいからみたいです。

# おわりに

以上、Zimbu言語の紹介でした。型破りな構文は熟考の上作られたものだったことが分かりました。
設計思想が他の候補付きで載っていて、言語設計の「決めの問題」にどう対処したかとでも勉強になりました。

今回は触れませんでしたが、Protocol Bufferも扱えるようなので機会があれば挑戦してみたいと思います。

[^editor]: おそらくVimのプラグインにすでにあるでしょう。~~私はVSCodeしか使えないのでプラグインがありませんでした~~
[^replace]: 子クラスが持っているメソッドが後で親クラスに追加されるのは「開放閉鎖の原則」に反する気もします...
[^include]: Zimbuは多重継承を許可されていないので、親クラスと別に便利メソッド群を継承することはできません。