🦌

Shiikaの型システム - メタクラスとジェネリクス

2021/12/25に公開

こんばんは。yharaです。メリークリスマス!本記事は言語実装 Advent Calendar 2021最終日の記事です。昨日は@sisshiki1969さんのrurubyのガベージコレクタとアロケータでした。

Shiikaの2021年

Shiikaは私が作っている静的型付け言語です。今年はenum、パターンマッチ、Maybe型といろいろ機能を足しました。来年はModuleと型推論をやる予定で、そこまでできると当初考えていた言語仕様が一通り揃います。いやー楽しみですね。はやくShiikaで実用的なプログラムを書きたい。

全てがオブジェクト

ShiikaはRubyの影響を強く受けており、Rubyの「手触り」を静的型言語で再現できないか?というのを考えています。その一環として、Rubyのもつ「全てがオブジェクト」という特徴を受け継いでいます。

Rubyでは全ての値がオブジェクトで、たとえば整数はIntegerクラスに属します。クラスもまたオブジェクトであり、Integerまたは1.classなどとすることでIntegerクラスを表すオブジェクトを取得できます。

メタクラス

Shiikaも「クラスもオブジェクト」という特徴を引き継いでいますが、その詳細がすこし違います。Rubyでは特異メソッドという仕組みでInteger.sqrtなどのいわゆる「クラスメソッド」を実現していますが、任意のオブジェクトにメソッドを足せるような仕組みは静的型付けでは難しいので、Shiikaでは代わりに「メタクラス」というものを導入しました。

クラスを1つ定義すると、それに対応したメタクラスが自動的に定義されます。メタクラスの主な役割は、「クラスメソッド」を保持することです。例えばShiikaでInt.sqrtメソッドを定義するとしたら、構文上は以下のようになりますが:

class Int
  def self.sqrt(x: Int) -> Int
    ...
  end
end

意味論としては、IntのメタクラスであるMeta:Intsqrtというメソッドを持つ、という定義になります。(ちなみにShiikaのプログラムを書くうえでは、このへんのややこしいことを考える必要はありません。あくまで、言語処理系を実装するうえでどうするか?という話です)

メタクラスとジェネリクス

さて、上記の仕組みをジェネリクスと組み合わせる方法は自明ではありません。Shiikaではclass Array<T>のように、型パラメータを取るクラスを定義することができます。いわゆるジェネリクスですね。

初期のShiikaでは、Array<Int>.newというプログラムを、「Array<Int>という名前の定数が存在し、その型はMeta:Array<Int>である」という形で定義していました。しかしこれには一つ問題がありました。以下のように、型引数の部分に型パラメータが来ることがあるんですよね。

class Foo<T>
  def bar
    ary = Array<T>.new
    ...
  end
end

このArray<T>について、現在のShiikaでは苦し紛れに「Array<T>という名前の定数がある」ということにしています。が、これはあまりにもアドホックです。

<>メソッド

そこで現在やっているのが、Array<Int>Array.<>(Int)、すなわち「Arrayの<>メソッドをIntを引数に呼び出す」と定義する改修です。(余談;まあメソッドにしなくてもいいんですけど、しておいたほうがコード生成器とかの実装がシンプルになるのでそうするつもりです。返り値の型が引数によって変化するため、型チェッカでは特別扱いする必要があります)

この変更により、例えば

class Foo<T>
  def bar
    p T
  end
end
Foo<Int>.new.bar

とすると#<class Int>という出力が得られるようになります。Tが、現在のselfに与えられた型引数への参照になるイメージですね。

別に、これができたからって何かいいことがあるわけではないですけど、一貫性って大事だと思うんですよね。Rubyも「数値などプリミティブな型もすべてオブジェクトである」っていうとこが好きだったりするんで。

おわりに

というわけで最近やっているShiikaのリファクタリングの話でした。ほんとは動くものを見せられたら良かったんですが実装が間に合わなかった。型チェッカまでは通っててあとはコード生成部を直せば、というところです。

Shiikaは「自分が欲しい物を作る」というコンセプトですけど、最近ちらほら見かける「雑に書けるRustが欲しい」という欲求にはある程度マッチするんじゃないかな…?と思っています。もちろん「雑」と「Rust」のどっちを重視するかは人によって違いそうですし、Shiikaはどちらかといえば「Rust」より「雑」寄りですけど。

Help Wanted

興味をもっていただいた方のために、リポジトリへのリンクを貼っておきます。

https://github.com/shiika-lang/shiika/

あと手が回ってないタスクも貼っておきます。実装言語はRustです。よろしくお願いします。

  • Add source location to ast and hir #251
    • エラーがあったとき、どのファイルの何行目でエラーになったか表示してほしい。
  • Pattern match
    • enumに対するマッチはできるんですけどwhen Some(123)とかwhen Some(true)みたいにリテラルも書けるようにしたい。

Discussion