Hash の key の同一性の判定について

Ruby のハッシュ (連想配列) に関して。 ハッシュは以下のように生成することができます。

hash = { "key1" => "val1", "key2" => "val2" }
そして key を指定して value を得ることができるわけですが、そのときに問題となるのがどうやって key の同一性を判定しているのかどうか、です。

例えば、String オブジェクトが 2 つあり、値として同一の場合。 このとき値は同一ですが、オブジェクトとしては同一ではありません。 ハッシュの key の同一性をどのようにして判定しているのかが重要になります。

str1 = "test"
str2 = "test"
# 値としては同一
p ( str1 == str2 ) ? true : false #=> true
# オブジェクトとしては同一ではない
p ( str1.equal? str2 ) ? true : false #=> false

Object#eql? メソッド

Ruby リファレンスマニュアルの Hash クラスに関するページ を見ると、以下のように書かれています。

ハッシュの格納に用いられるハッシュ値の計算には、Object#hash メソッド が使われ、キーの同一性判定には、Object#eql? メソッド が使われます。
さらに Object#eql? メソッドについて見ると、eql? のデフォルトの定義は equal? と同じくオブジェクトの同一性判定になってい ると書かれています。 ということは String オブジェクトを key にすると、同じ値でもオブジェクトとしては別だから key として同一とみなされないのでは・・・? と思ってしまいます。

しかし

h = { "test" => "a", "test2" => "b" }
p h["test"] #=> "a"
として実行してみるとちゃんと key として同一として判定されていました。

で、リファレンスマニュアルに書かれているとおりだとうまく同一判定されないはずなのに、なぜちゃんと同一だと判定されるのかについてちょっと調べてみました。 その結果 まつもとさんが書かれたこの文章中

それに合わせてというとちょっと違うと思いますが,eql? のデフォルトの定義は == をつかって
def eql?(other)
self == other
end
こういう感じになっています.
という記述を発見しました。 デフォルトのままだと、Object#eql? メソッドはオブジェクトとしての同一性をみるのではなくて、値としての同一性を判定するみたいですね。

てことでリファレンスマニュアルの記述が古い、またはおかしいようです。

Ruby の "Hello, world!"

院試が終わってから Yugui 著 『初めての Ruby』 (ISBN: 978-4-87311-367-8) を読んで Ruby の勉強をしてたんですが、ようやく本日読了しました! そんなわけで Ruby について初心者ながらにちょこちょこ書いてみようと思います。

とりあえず今日は標準入出力やらエンコーディングやらについて。

標準入出力

標準入力を表すオブジェクトは、組み込み変数 $stdin に格納されています。 その初期値は STDIN です。 同様に、標準出力について $stdoutSTDOUT標準エラー出力について $stderrSTDERR があります。 STDIN、STDOUT、STDERR は IO クラスに属するオブジェクトです。

$stdout や $stdin のメソッドを使用して標準出力へ出力したり、標準入力から読み込んだりすることができます。 $stderr はエラーメッセージの出力に使用されます。

# 出力
$stdout << "C++ 的な表記法" << "\n"
$stdout.write( "write メソッドを使用\n" )
# gets メソッドを使用して標準入力から読み込み
str = $stdin.gets()
# 入力された値を表示
$stdout << str

また、組み込み関数 putsprintp などは $stdout の write メソッドを呼び出して、標準出力に文字列を出力します。 同様に組み込み関数 gets は $stdin の gets メソッドを呼び出し、標準入力から文字列を得ます。 一般的には $stdout や $stdin を直接操作するのではなく、puts や gets などの組み込み関数を用い、標準入出力を間接的に使用することが多いようです。

ソースコードエンコーディング (マジックコメント)

Ruby 1.9 では文字列をバイト列としてではなく 「文字の列」 として扱えるようになりました。 例えば String オブジェクトの length メソッドは 「バイト数」 ではなく 「文字数」 を返します。 また、String オブジェクトは文字列のエンコーディングを知っています。

そのため、ソースコード内の文字列のエンコーディングが何であるかを Ruby 処理系に教えてやる必要があります。 そのための方法としてマジックコメントが使用されます。 次のようなコメント文をソースコード冒頭 (Shebang があるならその次の行) に書きます。

# -*- coding: utf-8 -*-
上の例の場合、Ruby 処理系はソースコード内の文字列のエンコーディングUTF-8 だとみなして処理します。 以下のように書いても良いです。
# vim:fileencoding=UTF-8

外部エンコーディングと内部エンコーディング

Ruby 1.9 では、IO クラスが外部とデータのやり取りする際に文字エンコーディングを考慮するようになりました。 これは Perl IO レイヤと似ています。

「外部エンコーディング」 というものを指定しておけば、IO クラスが外部に文字列データを出力する際に 「外部エンコーディング」 で指定されたエンコーディングに変換して出力します。 外部からデータを受け取る際も同様で、受け取ったバイト列を 「外部エンコーディング」 で指定されているエンコードされた文字列だと解釈します。

また 「内部エンコーディング」 というものもあります。 内部エンコーディングが指定されていない場合は、外部から読み込んだデータは 「外部エンコーディング」 で指定されたエンコーディングの文字列として Ruby の内部に持ち込まれます。 しかし、「内部エンコーディング」 が指定されていれば、そのエンコーディングに自動的に変換されます。

組み込み定数 STDOUT なども IO オブジェクトなので、外部エンコーディングや内部エンコーディングを指定することができます。 ソースコードUTF-8 で書いているのに、システムのエンコーディングShift_JIS だったりすると、そのままでは文字化けしてしまいます。 そのような問題を起こさないために STDOUT の外部エンコーディングをシステムのエンコーディングにしておくと良いでしょう。 外部エンコーディングおよび内部エンコーディングの指定には IO#set_encoding メソッドを使用します。

# 外部エンコーディングをシステムのエンコーディングに, 内部エンコーディングを UTF-8 に変更
STDOUT.set_encoding( Encoding.locale_charmap, "UTF-8" )

サンプルコード

というわけで今日の内容をまとめたサンプルコード。

#! /usr/bin/ruby1.9
# -*- coding: utf-8 -*- # ソースコードのエンコーディング指定

# 標準入出力の外部エンコーディングをシステムのエンコーディングに変更
# 内部エンコーディングを UTF-8 に変更
STDIN .set_encoding( Encoding.locale_charmap, "UTF-8" )
STDOUT.set_encoding( Encoding.locale_charmap, "UTF-8" )
STDERR.set_encoding( Encoding.locale_charmap, "UTF-8" )

# 引数の文字列を標準出力に出力し, 改行する
puts "Hello, world!"
# 引数の文字列を標準出力に出力する
print "何か入力してください: "
# 標準入力から文字列を得て変数 str に代入する
str = gets().chomp() # gets で標準入力から文字列を得て, chomp で改行コードを削除
# 入力された文字列とそのエンコーディング (UTF-8) を表示
print "入力された文字列: ", str, ", エンコーディング: ", str.encoding, "\n"