はじめに
RubyのString#strip
が全角スペースをトリミングしてくれないことにこの間初めて気づきました。
そこでフィヨルドブートキャンプのSlackで質問してみたところ、結構盛り上がったので記事にまとめます。
本編
自分で調べたこと
まず自力で調査したことをまとめます。
Javaのtrimメソッド
Javaのtrim
のロジックは「\u0020
(半角スペース)の文字コードより大きいか小さいか」が判断基準になっています。
ただ、それが他の言語の場合同様のロジックで実装されているのかまではわかりませんでした。
RubyのString#strip(Cのコード)
前後のオフセット値を算出してそれを利用して前後のスペースのトリミングを実現しています。
ただ、C力が圧倒的に足りないのでどういうロジックでそのオフセット値を算出しているかまではわかりませんでした。
追記
RubyのRuby実装、Rubiniusで追ってみたところ、strip
の対象かどうかは「ASCIIでの文字コード(10進数)が32または9以上13以下」が基準となっていました。
浮かんだ疑問
- なぜJavaでは文字コードの大小が判断基準になっている?
- 処理速度?
- C標準ライブラリには正規表現関連の機能がないから?
メンター達の見解
メンターの方々から様々な意見をもらえたので、それも紹介します。
@komagataさん
- Rubyはともかく英語圏の人がメインで作ってる言語なら
String#strip
で全角を扱わないのは自然では? - 自分はプログラム的に意味を持たない半角スペースだけを処理してほしい。全角も処理したい場合自分でやるので。
自分の場合は、日本人以外が開発した言語が全角を扱わないのはともかく、日本人のMatzが作ったRubyが全角をトリミングしないのはなぜだろう、と不思議に感じてしまいました。
ただ、これに対しては@spaceprobeさんから下記の意見をいただきました。
- Rubyの作者は日本人かもしれないが、日本人に便利に使ってもらおうと作ったものではない(はず)
- 他のプログラミング言語にも似たメソッドがある中で特定の言語だけ違った挙動を示す時、開発者からいい顔をされないのでは。
特定の言語だけ違った挙動だった場合の話が非常に納得いきました。
似たような話で、三項演算子に関して後日こんなツイートもありました。
true ? 'a' : false ? 'b' : 'c'
— 猫ぱんち (@nekopunch_pk) 2019年6月18日
C++「'a'」
ruby「'a'」
js「'a'」
PHP「'b'」
ファッ? https://t.co/dFKYNUtbsc
こんな挙動の違いがあったら確かに戸惑いますね・・・。
また、@spaceprobeさんからは下記のようなお題もいただきました。
- 全角スペースと、
trim
の対象になっている文字群にはどのような違いがあるでしょうか?
これに対する答えとしては「ASCII文字かそうでないか」という違いがありました。(trim
の対象となるのはASCII文字)
@igaigaさん
- 「
trim
は全角スペースも対象にしてもいいんじゃないか」派- 正規表現の
\p{blank}
は全角スペース(ASCII以外のスペース)も対象だから。(\s
はASCIIだけ。)
- 正規表現の
- Rubyのコードでも一部の場所では全角も使えるので、もはや半角スペースの代わりに全角スペースがつかえるようになってもいいんじゃないかと思う。
- 乱暴……だけど先生業してるときは楽
Rubyのコードでも全角を扱える場所があるとは知りませんでした・・・。
は今のRubyでも動くそうです。
ruby
# Fが全角
class Foo
def foo
"foo!"
end
end
p Foo.new.foo #=> foo!
@june29さん
- ウェブアプリケーション開発者として、全角スペースを
strip
したいユースケースは理解できる-
ActiveSupport
のblank?
にも似た話だと捉えていて、言語としてnil
と空文字は明らかに別物だけど、ユースケースとして「利用者がなにも入力しなかった」という点では同じだから、ライブラリで抽象化したって感じ
-
- 言語仕様として全角スペースをどう扱うかについては自分はけっこう保守的かも
- 全角の
@
と半角の@
は別物やろ、と思う - この問題は本当に難しい、半角の記号のひとつひとつについて (たとえば丸括弧とか) はどうするの?と考え始めると、やっぱり全角の記号類は半角のものとは完全に別物としておきたい気もする
- 全角の
- @igaigaさんの「半角スペースの代わりに全角スペースがつかえるようになってもいいんじゃないか」という意見にも共感しつつ、入力者が「自分が入力した文字が全角なのか半角なのかわからずやっている」状況を助長したくはない、という強い気持ちもある
2点目なんかは検索機能とか作ってても全角と半角を一緒に扱おうとすると工数が莫大に増えてくるので確かにそうだなと感じます。
@udzuraさん
注:あくまで思考実験レベルの推測。
- そもそもShift-JIS/EUC-JP中心の時代から長年「全角」と「半角」が存在してきた日本語のエンコーディング文化では、全角半角のスペースも当然違うものだと区別する「文化」ができていた
- 一方海外では、そもそもマルチバイト文字はUTF-8という考えが多く、その場合UTF-8正規化の仕様も同時に入ってきているので、「同じ意味の文字」はコードポイントが異なっても同じに扱うべきという考えが主流になっているのかも
- なので、逆に新しい言語ほど、全角/半角のスペースもプログラムから同じように扱う
- Rubyでも全角/半角のスペース正規化はできたりする。(下記コード参照。バージョンは2.5.1。)
irb(main):004:0> " ".bytes
=> [32]
irb(main):005:0> " ".bytes
=> [227, 128, 128]
irb(main):011:0> " ".unicode_normalize(:nfkc).codepoints
=> [32]
※@udzraさんによると、まさに今年のRubyKaigi(2019)でこの関係の話をした方がいた記憶があるそうなので、自分も探してみて見つけたら追記します。この記事を読まれた方で覚えていらっしゃる方いれば教えてください。
2019/06/19 19:00 追記
@udzuraさんに教えていただきました、ありがとうございます!
RubyKaigi2019でのUnicodeに関する発表(16:04〜21:20)https://t.co/2LnYi2J2bD
— 森塚 真年 (@sanfrecce_osaka) 2019年6月19日
結局どうなの?
すみません、結論は出てないです
メンターの方々の意見をまとめると全角スペースをトリミングするかどうかは全角スペースとASCIIでのスペースを同じ「スペース」として扱うかどうかという思想が焦点になってくると思います。
ただ、メンターの方々の間でも結構色んな意見が出ましたし、言語ごとで実装の差異があった場合に困惑する問題もあるのでかなり難しい問題なのかな、という感想です。
最後に
とはいえ全角をトリミングしたいことは結構あると思ってて、その度にString
を拡張するのは面倒くさいのでGemを作りました。
Gem名はタ◯ノコの某アニメのタイトルをもじってます。
GitHub: https://github.com/sanfrecce-osaka/hurricane_trimar
RubyGems: https://rubygems.org/gems/hurricane_trimar