追記あり
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.