Mojoのパラメータ(コンパイル時メタプログラミング)を使ったFizzBuzzを書いてみる

Mojoが発表されて早速盛り上がっていますね。

LLVMやSwiftの作者であるChris Lattner率いるチームが開発した新しい言語ということで、Swift好きな自分としてももちろん興味があります。現時点ではPlaygroundにアクセスするのにウェイトリストに並ぶ必要がありますが、自分もアクセスできるようになったので早速ちょっと遊んでみます。

Mojoにはコンパイルメタプログラミングを可能とするパラメータという機能があって、これを遊ぶにのにFizzBuzzを書いてみましょう。

早速コードを書いてみるとこんな感じです:

fn parameterizedFizzBuzz[n: Int, counter: Int]():
    @parameter
    if counter > n:
        return
    else:
        if counter % 15 == 0:
            print("FizzBuzz")
        elif counter % 3 == 0:
            print("Fizz")
        elif counter % 5 == 0:
            print("Buzz")
        else:
            print(counter)
        parameterizedFizzBuzz[n, counter + 1]()

通常の引数(argument)は () に引数リストを定義しますが、パラメータ(parameter)は [] に定義します。また @parameter if を使うことで、コンパイル時にif文を実行することができます。

This makes use of the @parameter if feature, which is an if statement that runs at compile time. It requires that its condition be a valid parameter expression, and ensures that only the live branch of the if is compiled into the program.

これを実際に実行してみると正しく動作します。

parameterizedFizzBuzz[60, 1]()

ちなみに2023年5月7日現在のPlayground環境のトップレベルコードとしては60回が限界でした。61にすると elaborator expansion is 129 levels deep - infinite recursion? というエラーになりました。

error: Expression [6]:21:1: no viable expansions found
fn __lldb_expr__(__mojo_repl_arg&: __mojo_repl_context__):
^

Expression [6]:23:28:   call expansion failed - no concrete specializations
    __mojo_repl_expr_impl__(__mojo_repl_arg)
                           ^

Expression [6]:34:26:     call expansion failed - no concrete specializations
  __mojo_repl_expr_body__()
                         ^

Expression [6]:31:33:       call expansion failed - no concrete specializations
    parameterizedFizzBuzz[61, 1]()
                                ^

Expression [6]:18:46:         call expansion failed - no concrete specializations
        parameterizedFizzBuzz[n, counter + 1]()
                                             ^

Expression [6]:5:1:                                                                                                                                 elaborator expansion is 129 levels deep - infinite recursion?
fn parameterizedFizzBuzz[n: Int, counter: Int]():
^

この elaborator というのは、ロードマップの「Protocols / Traits」というセクションで少し触れられていました。コンパイルのパイプライン中でパラメータ化されたコードをインスタンス化する役割のようですね。ここの再帰に制限があるのは納得できます。

Unlike C++, Mojo does not “instantiate templates” in its parser. Instead, it has a separate phase that works later in the compilation pipeline (the “Elaborator”) that instantiates parametric code, which is aware of autotuning and caching. This means that the parser has to perform full type checking and IR generation without instantiating algorithms.

パラメータではなく引数を使った場合は1万回や10万回でも問題なかったので、普通に末尾再帰最適化もされているということでいいのかな:

fn fizzBuzz(n: Int, counter: Int):
    if counter > n:
        return
    else:
        if counter % 15 == 0:
            print("FizzBuzz")
        elif counter % 3 == 0:
            print("Fizz")
        elif counter % 5 == 0:
            print("Buzz")
        else:
            print(counter)
        fizzBuzz(n, counter + 1)

ということでMojoのパラメータで少し遊んでみました。

Mojoは現時点ではPlaygroundが公開されているだけで、手元で動かすこともできませんし、オープンソースでもありませんが、すでにGitHubリポジトリは用意されてバグレポートやフィーチャーリクエストも受け付けています。

正式リリースまではクローズドな形で開発が進み、OSS化まではしばらく時間が掛かりそうではありますが、ロードマップやChangelogもチェックしながら今後を楽しみにしていきたいですね。