Rubyと違いのあるCrystalの文字列の注意点

最初に

Crystal と Ruby はよく似た言語だが、様々な点で違いがある。

Ruby から Crystal の文字列を扱うときの注意点を書いてみる。

Crystalには文字がある

Ruby には文字列という概念しかないが、Crystal には文字と文字列という概念がある。

C言語などを学習したことがある人にとっては馴染み深いだろうが、
文字と文字列で区別するとき、

  • 文字 …… 1文字で'で囲む。
  • 文字列 …… 0文字以上で"で囲む。

という違いがある。

Ruby の文字列をシングルクォートで書いてると、Crystal に移植するとき、"に変更する必要があって面倒。

Crystalの文字列はイミュータブル

Ruby の文字列は、ミュータブルなもので、破壊的メソッドで破壊的な変更をすることができる。
しかし、Crystalの文字列は、イミュータブルで、破壊的メソッドはない。

イミュータブルにすると速くなることがあったり、コードとしての安全性が増す等のメリットがあるようだ。
なお、PythonJavaScript などの言語で、文字列はイミュータブルものである。

Ruby も昔は、Ruby 3.0では文字列はイミュータブルなものに変更しようという動きがあったが、
結局、後方互換性に大きな問題があるということでなくなった。 なお、後方互換性のない大きな変更をしたところで、大きなメリットがないという批判もあった。

具体的にできないこと

str = "Hello!"
p str[0]  # => 'H'
p str[2..3]  # => "ll"

Crystal は[]で文字や文字列をとることができるが、 Crystalの文字列はイミュータブルなので[]=の形で文字列の中の文字を変更することができない。

Crystalの文字と文字列の結合

文字と文字列は、足すことができる。

p "Hello" + '!'  # => "Hello!"
p '!' + "Hello"  # => "!Hello"

しかし、文字と文字は足すことが、できない。

p '!' + '?'
# Error: no overload matches 'Char#+' with type Char
# 
# Overloads are:
#  - Char#+(str : String)
#  - Char#+(other : Int)

このエラー文を見てわかるが、文字にはInt型の数値を足すことができる。

p 'b' + 1 # => 'c'
p 'b' - 1 # => 'a'

これは、Ruby にはない機能だ。競プロとかだと、ちょっと便利かもしれない。

Crystalの文字と文字列の比較

p 'a' == "a" # => false
p 'a' == 97  # => false
p 'a'.ord       # => 97

Crystal の文字と文字列は足すことができたが、同じだろうと思うものでも比較をするとfalseを返してくる。
このあたりは非自明で、注意ポイントだと捉えている。

Crystal の文字は、倍にできない。

RubyでもCrystalでも、文字列は倍などにできる。

p "a" * 2  # => "aa"

しかし、Crystalの文字に対して、*を使えないので注意が必要。

'a' * 2
# error in line 1
# Error: undefined method '*' for Char

実行しようとすれば、定義されてないので、Charに対して*は未定義というエラーで怒られる。

String#chars

RubyString#charsは、文字列から文字の配列を作るだけでなく、ブロックで文字を回すこともできる。

しかし、CrystalのString#charsは、文字の配列を作るだけで、ブロックを取ることはできない。

"str".chars{ |c| p c }
# Error: 'String#chars' is not expected to be invoked with a block, but a block was given

Ruby でも、 Crystal でも、文字列の文字を回したくなったら、each_charを使おう。

"str".each_char{ |c| p c }

Crystal の全体的な方針としてエイリアスは作らない方針があり、ブロックをとって回すメソッドはeach_を接頭辞(prefix)でつけると考えてよい。

なお、Crystal のcharseach_charも、要素は文字列Stgringではなく、文字Charとなることに注意。

p typeof("str".chars) # => Array(Char)

String#bytes

RubyString#bytesは、文字列からバイトの配列を作るだけでなく、ブロックでバイトを回すことができる。

こちらもchars同様、Crystal ではbytesでブロックをとって回すことはできない。
ブロックをとって回すときは、each_byteを使う。
Ruby でも、charsbytesでブロックをとって回すのは少しお行儀が悪いので、each_chareach_byteを使いましょうという話。

また、Crystal の返す配列の要素は、Int32などではなく、Int8型である点に注意。

Crystalは、三単現

文字列に限った話ではないが、Crystal は三単現(三人称・単数形・現在形)で統一する方向性である。

Ruby でのString#start_with?String#end_with?は、CrystalではString#starts_with?String#ends_with?である。

JavaScript でも同名の三単現のメソッドがあるので、JavaScript使いは馴染みやすそうである。

最後に

ザーッと Ruby から Crystal の文字列を扱うときの注意点を書いてみたが、いかがでしたでしょうか?