rubyの書き方
今回はシンボルと破壊的メソッドを言語化します。
シンボルについて
シンボル、書き方が多すぎるように見えて、何だかよくわからない。コロンが前だったり、後ろだったりで、どれもシンボル・・・統一感が無いようにしか見えなかった。読んだコードはいろんな書き方が散見され、メリットだったりもどれも判然としなかったからである。
しかし、そうではなかった。シンボルに書き方がいっぱいあるのではなく、ハッシュの書き方が様々なのだった。
シンボルは名前の前にコロンを置いた表記でしかなかった
:key
上記がシンボルである。
私が混乱したのは下記のように4パターンも書き方が存在し、それにシンボルに見えるような書き方とが入り混じっているせいで、これをシンボルと勘違いした事がそもそも理解の遅さに繋がってしまった。
日本語でもバリューにアクセスできるのはちょっと不思議な感じがします。
rubyのバージョンは
ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19]
です。
# 普通のハッシュ
nomal_hash = {
"Avdol" => "Magician's red",
"Igy" => "the fool",
}
nomal_hash_two = {
"空条承太郎" => "スタープラチナ",
"花京院典明" => "ハイエロファントグリーン",
}
# シンボルをキーとして定義したハッシュ
symbol_hash = {
:Avdol => "Magician's red",
:Igy => "the fool",
}
symbol_hash_two = {
:空条承太郎 => "スタープラチナ",
:花京院典明 => "ハイエロファントグリーン",
}
# シンボルがキーのとき、JSONのように略記することができる
json_like_hash = {
"Avdol": "Magician's red",
"Igy": "the fool",
}
json_like_hash_two = {
"空条太郎条": "スタープラチナ",
"花京院典明": "ハイエロファントグリーン",
}
# キーが変数名としての要件を満たすような単純な英数字のみを使ったシンボルであれば
# ダブルクォートが要らない
# これは JavaScript のオブジェクト記法と同じような見た目となる
no_double_quote_hash = {
Avdol: "Magician's red",
Igy: "the fool",
}
no_double_quote_hash_two = {
空条承太郎: "スタープラチナ",
花京院典明: "ハイエロファントグリーン",
}
# それぞれのアクセスの仕方
# ノーマルなハッシュは文字列キーを使う
puts "Avdolのスタンドは #{nomal_hash["Avdol"]} です(文字列キー)"
puts "空条承太郎のスタンドは #{nomal_hash_two["空条承太郎"]} です(文字列キー)"
# シンボルで作られたハッシュはシンボルキーを使う
puts "Avdolのスタンドは #{symbol_hash[:Avdol]} です(シンボルキー)"
puts "空条承太郎のスタンドは #{symbol_hash_two[:空条承太郎]} です(シンボルキー)"
# JSON風に書いたハッシュもシンボルをキーにする
puts "Igyのスタンドは #{json_like_hash[:Igy]} です(シンボルキー)"
puts "花京院典明のスタンドは #{json_like_hash_two[:花京院典明]} です(シンボルキー)"
# クォートを必要としないJSON風に書いたハッシュもシンボルをキーにする
puts "Igyのスタンドは #{no_double_quote_hash[:Igy]} 点です(シンボルキー)"
puts "花京院典明のスタンドは #{no_double_quote_hash_two[:花京院典明]} 点です(シンボルキー)"
出力結果
Avdolのスタンドは Magician's red です(文字列キー)
空条承太郎のスタンドは スタープラチナ です(文字列キー)
Avdolのスタンドは Magician's red です(シンボルキー)
空条承太郎のスタンドは スタープラチナ です(シンボルキー)
Igyのスタンドは the fool です(シンボルキー)
花京院典明のスタンドは ハイエロファントグリーン です(シンボルキー)
Igyのスタンドは the fool 点です(シンボルキー)
花京院典明のスタンドは ハイエロファントグリーン 点です(シンボルキー)
文字列オブジェクトと違うシンボル(オブジェクト)の特徴は、同じ文字列で生成した場合にオブジェクトとして同一かどうか。
irb(main):001:0> x_str = "key"
=> "key"
irb(main):002:0> y_str = "key"
=> "key"
irb(main):003:0> x_str.object_id
=> 70221000750740
irb(main):004:0> y_str.object_id
=> 70221000769360
irb(main):005:0> x_sym = :key
=> :key
irb(main):006:0> y_sym = :key
=> :key
irb(main):007:0> x_sym.object_id
=> 161628
irb(main):008:0> y_sym.object_id
=> 161628
x_str.object_id
と y_str.object_id
は違うが、 x_sym.object_id
と y_sym.object_id
は同じ。
Ruby では x_str === y_str
を true
としてくれるものの、ハッシュキーにシンボルを使うことが好まれる理由の一つに上記の性質が挙げられるようです。
破壊的メソッド
データの渡り方3種
値渡しと参照渡しと参照の値渡しと言う話ですね。自分は初めてこれを聞いた時に、データの渡り方にはコードの中で、様々あると思っていたのですが、このデータの渡り方は各プログラミング言語によって、方式として、採用しているものと採用していないものが存在します。つまり、採用方式が言語によって違うのだなという認識が正しいのだと思われます。例えば、rubyには値渡しも参照渡しも存在せず、参照の値渡しが行われます。
値渡し は、データそのものを送りむ事です。x = 200なら、200 というデータ(数値リテラル)そのものがコピーされて渡されていることを意味します。すべてのデータが参照型のオブジェクトである Ruby ではこのような素朴な値渡しではなく、後述の参照の値渡しが行われるようです。
参照渡し は、後述の参照の値渡しと混同されるケースが散見されますが、純粋な参照渡しをサポートしているプログラミング言語は少数派のようです(参考:引数 - Wikipedia)。Ruby も例に漏れず参照渡しはサポートされず、参照の値渡しが活躍するようです。参照渡しは、確保したデータ領域を直接渡している感覚ですね。
下記のようなPerlで書いた言語では参照渡しのデータのやり取りが採用されています。(Perlを自分は全然よくわかっていない・・・Rubyもまだまだですが・・・)
use strict;
use warnings;
my @numbers = (111, 222, 333);
for my $number (@numbers) {
# Perl の $number は Rubyのnumbers[0] とかと同じことを書いたとみなされる
# 下記で書いたRubyコードのnumbers[0] = numbers[0] ** 2 のようなことが [0] [1] [2] で順番におこる
# この $number と numbers[0] が全く同じデータ領域を指している状態が参照渡し
$number = $number ** 2;
}
では、参照の値渡し は何か・・・
新しく作成したデータ(オブジェクト)の場所を渡す
という事ができるはず・・・。
場所の次にその場所から、データを取り出す作業が必要になるはずです。
先のPerlで書いたコードをRubyで書いてみます。
numbers = [111, 222, 333]
count = 0
# numbers を2乗したいな〜
numbers.each do |number|
number = number ** 2
end
# 当然 [12321, 49284, 10889] にならない
p numbers
参照渡し的な書き方をしても、この上記のRubyのコードは動きません。何も出力されません・・・そう、参照渡しがRubyには採用されていないからですね。
動かすには、データを一旦取り出してやる作業が必要になってきます。参照の値渡しをするコードの書き方をしないといけません。場所を直接指定せず、場所にあるデータをRubyには伝えてあげるように書きます。実際に配列からデータを指定するような書き方になっていると思います。
numbers = [111, 222, 333]
count = 0
numbers.each do |num|
numbers[count] = numbers[count] ** 2
count += 1
end
p numbers
メソッドを使うともっと簡単になります。メソッドがこんなに豊富なのがRubyのメリットですね。
numbers = [111, 222, 333]
mapping_number = numbers.map {|num| num ** 2}
p mapping_number
話がmapメソッドのことで逸れましたが、これがデータの渡り方の違いになってくるのだと思います。プログラミングは状態管理、とするならば、データの渡り方はしっかり理解すべきと思ったので、纏めました。
さて、本題の破壊的メソッドについてです。
# 代入
p story = "bakemonogatari"
# 代入の代入
x = story
# 破壊的メソッドに渡す
p story.upcase!
# 全部同じになる
p story
p x
p "##################################"
# 代入
p story_two = "tukimonogatari"
# 代入の代入
y = story_two
# 非破壊的メソッドに渡す
p story_two.upcase
# upcaseメソッドの影響を受けていない
p story_two
p y
出力結果
"bakemonogatari"
"BAKEMONOGATARI"
"BAKEMONOGATARI"
"BAKEMONOGATARI"
"##################################"
"tukimonogatari"
"TUKIMONOGATARI"
"tukimonogatari"
"tukimonogatari"
Ruby の組み込みライブラリでは、メソッド名の末尾が !
のメソッドは破壊的となる。破壊的、というのはオブジェクトを編集するという能力を持つことを意味する。編集し、新しく作られたオブジェクトの場所を示しているので、storyもxも新しく作られたオブジェクトの値のある場所を参照しているので、大文字変換された文字列をputsしている。
逆に非破壊的メソッドは、オブジェクトを新しく作っても、新しいオブジェクトの参照部分を渡す事が無いので、upcaseメソッドの影響が無いように見える。
# 代入
p story = "bakemonogatari"
# 代入の代入
x = story
# 破壊的メソッドに渡す
p story.upcase!.object_id
# 全部同じになる
p story.object_id
p x.object_id
p "##################################"
# 代入
p story_two = "tukimonogatari"
# 代入の代入
y = story_two
# 非破壊的メソッドに渡す
p story_two.upcase.object_id
# upcaseメソッドの影響を受けていない
p story_two.object_id
p y.object_id
出力結果
# 全部同じID
"bakemonogatari"
70291624072300
70291624072300
70291624072300
"##################################"
# 作り直されたやつだけ違うオブジェクトID
"tukimonogatari"
70291624071720
70291624071800
70291624071800
破壊的と非破壊的でオブジェクトIDに違いがある。
なお、 String#upcase!
の返り値は破壊した文字列オブジェクト自身である(参考:String#upcase! (Ruby 2.7.0 リファレンスマニュアル))。なので p story.upcase!.object_id
は story.upcase!
後に p story
で破壊後の story
を出力する2段階の操作の略記となっている。
まとめ
ややこしいけど、ようやく言語化できたと思います。間違っていたら指摘ください。