Previous ToC Next

143. Crystal によるメタプログラミング (2018/8/11)

Ruby のような動的言語を使うことの大きなメリットは、メタプログラミング を容易にすることでしょう。実行時にデータからモジュールやクラスを新しく 生成して、それらを元々あったクラスと同じように使えるため、汎用 的なコードを書きやすく、無駄な繰り返しを避けることができます。

とはいえ、Ruby はインタプリタ言語であることによる実行速度のペナルティが 大きく、また動的であることの結果として、様々なエラーが実行時にしか検出できないという問題があります。

その辺の問題を解決しようという試みに1つが Crystal 言語で、普通に書く分 には Ruby に近い文法で、変数の型とか気にしないでプログラムを書けるので すが、強力な型推論によりあらゆる変数の型はコンパイル時に決まり、またコ ンパイルされる言語なでのC/C++ に劣ることがない速度で実行されます。

とはいえ、そういうわけで、実行時にクラスの追加とかはできません。

一方、メタプログラミングの用途として、コンパイル時にできればよいことは 沢山あります。典型的なのは、なんらかのテキスト(仕様記述とか他の言語の プログラムとか)をテキスト処理(これ自体は Ruby なりCrystal で記述して) してできるクラス定義を取り込む、といった場合でしょう。

例えば、プログラムに埋め込まれた文字列定数からクラスライブラリを生成す ることを考えます。Ruby なら、そういう、文字列からクラス定義のコードを 生成する関数を作っておいて、(これを foo としましょう)

   s =<<-EOF
   some text
   ...
   ...
   EOF
   eval(foo(s))
みたいなコードを書けばすむところです。

Crystal にはもちろん eval はないので、こんなことはできません。が、 これに相当することを実現することができるコンパイル時マクロ機能が あります。以下は、文字列リテラルで与えたコードがコンパイル時にそのまま 評価される例です。

  macro echo(s)
  {{ run("./echo", s)}}
  end
  
  echo(
  <<-END
  class Test
    property a
    def initialize(@a : Int32)
    end
    def pp
      p @a
    end
  end
  END
  )
  x= Test.new(1)
  p x.a
  x.a=2
  x.pp
これは、 echo というマクロが、 echo.cr という Crystal プログラムを 与 えられた引数を実行時コマンドライン引数として実行した結果のコードになる、 というもので、 echo を呼ぶ時に class 定義をここではそのまま文字列で 与えています。

echo.cr の中身は

  print ARGV[0]
だけで、ここでは引数をそのまま出力するだけです。なので、このプログラム 自体は、

  class Test
    property a
    def initialize(@a : Int32)
    end
    def pp
      p @a
    end
  end

  x= Test.new(1)
  p x.a
  x.a=2
  x.pp
というプログラムと同じで何の芸もありません。が、echo.cr をもっと複雑な 処理をするものに置き換えれば、コンパイル時に任意の文字列を好きなように 処理して生成したソースコード(少なくともクラス定義)を取り込めるわけです。 もちろん、ファイル名をマクロに渡すこともできるので、何かファイルの中身 を加工してできるテキストをソースコードとして解釈することもできます。

まあその、何がしたいかというと、昔 Ruby で書いた N 体計算用諸々を大体 Crystal で動くようにしてさらに FDPS も使えるようにしたいわけです。

FDPS を使うには少なくとも現状では C++ の(ほぼダミーの)メインプログラムから Crystal の本体プログラムを呼べる必要があります。現在の Crystal のドキュメントには どうすればできるかあんまり書いてない気がしますが、とりあえずできたので まあ、、、というところです。
Previous ToC Next