追記あり
RubyからCrystalへ移植しようとして躓いた点その1。
Rubyだとオブジェクトに to_s() メソッドが定義されていれば,puts や文字列内の式展開で暗黙のうちに to_s() で文字列変換された結果が使用される。一方,Crystalでは to_s() を定義しただけでは,暗黙の文字列変換で使用されない。
class Person
def initialize(name)
@name = name
end
def to_s
"Hi, I'm #{@name}."
end
end
puts Person.new("John")
上記のRubyソースコードは,Crystalでも問題なく実行可能だが,その実行結果はRubyとは異なっている。
$ ruby person.rb
Hi, I'm John.
$ crystal person.rb
# <Person:0x10cda3ec0>
Crystalでオブジェクトが暗黙の文字列変換を行うためには,to_s() ではなく引数に IO オブジェクトを取る to_s(IO) を実装しなければならない。(Crystalではオーバーロードが可能)
この to_s(IO) で,変換後の文字列を to_slice で Sliceオブジェクト化したものを,IO オブジェクトの write メソッドに与えてやる。
例えば,
class Person
def initialize(name)
@name = name
end
def to_s
"Hi, I'm #{@name}."
end
def to_s(io : IO)
io.write to_s.to_slice
end
end
puts Person.new("John")
のようにすることで,暗黙の文字列変換が機能するようになる。
$ crystal person.rb
Hi, I'm John.
(とはいえ,同じ to_s という名前なのに to_s(IO) が文字列を返さないのはどうなんかなぁ)
追記
コメントで指摘していただいた内容を追記します。
Rubyにおける to_s と同じことをしたい場合, to_s() はオーバーロードせず, to_s(IO) の IO オブジェクトに出力したい内容を与えてやれば良い,との事です。
よくよくソース見をたら,Object#to_s() の中で to_s(IO) が呼ばれてたし,ドキュメントにも
def
to_s
Returns a string representation of this object.Descendants must usually not override this method. Instead, they must override
#to_s(io), which must append to the given IO object.
って書いてあった。
Rubyの意識で to_s() が文字列を返すもの,という先入観があって to_s() のオーバーロード有りきで考えてしまったのが良くなかったです。反省。
というわけで,コメントで示してもらった代替案が以下のコード。
class Person
def initialize(name)
@name = name
end
def to_s(io : IO)
io << "Hi, I'm " << @name << "."
end
end
puts Person.new("John")
$ crystal person2.rb
Hi, I'm John.