はじめに
タイトルの通りです。RubyとCrystalは文化が違うので、ところどころ挙動が違います。
面白いことにCrystalはStringを継承できません。たとえば、
class MagicSpell < String
end
を実行すると、次のようなエラーがでます。
Error: Cannot inherit from String
これがどのように実現されているのかというと、
inherit マクロ
inheritマクロを使って書かれていました。使い方はとても簡単で、こんな感じです。
class String
macro inherited
{{ raise "Cannot inherit from String" }}
end
end
RubyでもClass#inheritedがありますが、それと同じように使えるようですね。
このマクロがどのように定義されているのか調べようと思いましたが、よくわかりませんでした。Crystalでは、マクロは
macro define_method(name, content)
def {{name}}
{{content}}
end
end
このような感じで定義します。そのため macro inherited
という記法はそれ自体がマクロの定義になってしまいます。
ここのコードを見ると、inherited
マクロは、他のいくつかのマクロ(included
, extended
, method_added
, method_missing
)などとともに名前のレベルで特別扱いされているようです。そんな用語はないですが、マクロの予約語といった感じでしょうか。
def add_macro(a_macro)
a_macro.owner = self
case a_macro.name
when "inherited"
return add_hook :inherited, a_macro
when "included"
return add_hook :included, a_macro
when "extended"
return add_hook :extended, a_macro
when "method_added"
return add_hook :method_added, a_macro, args_size: 1
when "method_missing"
check_macro_param_count(a_macro, 1)
else
# normal macro
end
このうち method_missing
に関しては、開発者によって追加したことを後悔している発言もあったはずで、あまり推奨ではないと思いますし、これらの特別なマクロについてAPIリファレンスをググってもあまり出てきませんので、Crystalでも一応できるけど推奨はされていない黒魔術ということになると思います。
Stringが継承できないことの意味
私はRubyの動的な性質やダックタイピングが好きです。しかし静的言語のCrystalで、Stringを継承したライブラリが無数に作られると、型の不整合で問題が発生しやすくなる気はします。文字列に関しては、標準ライブラリのStringを拡張せずそのまま使ってほしいというメッセージだと理解しました。