こんにちは。ふと気になり質問します。
Rubyには、メソッドに渡すオブジェクトを変更する破壊的メソッドには!を付ける慣習がありますよね。
他言語でそのような慣習は特に聞いたことないような気がしますし、じゃあ破壊的メソッドをどうやって区別してるの?と気になり質問します。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答5件
0
少なくとも私はRuby以外で聞いたことが無いです。他の言語は、メソッド名で区別すると言うよりは、immutable(変更不可)なクラスなのかmutable(変更可能)なクラスなのかで区別しているような事が多い気がします。同じメソッド名でもレシーバ(メソッドが呼び出されるオブジェクトのこと。a.b(c)
とした場合、aがレシーバ、bがメソッド、cが引数になる)に対する副作用があるかどうかはレシーバのクラスの型で判別できるからメソッド名で区別する必要が無いという事だと思います。
###Rubyの破壊的メソッドをよく知らない人へ
Rubyではほとんどのオブジェクトがmutable(変更可能)です。しかし、最近流行り__らしい__関数型プログラミングでは、オブジェクトがimmutabel(変更不可)であることや、関数が純粋関数(副作用が無く、引数が同じなら、戻り値も常に同じになる関数)であることが重視されつつあります。そんな最近の流行とは関係なく、初期の頃から、Rubyではオブジェクトが変更されるのかされないのかが重視されてきました。
さて、今、文字列を扱っているとします。Rubyで文字列を扱うStringはmutableです。その文字列を全て大文字にした文字列が必要になったとします。Rubyで大文字にするメソッドはString#upcase
です。では、upcase
を呼び出した後に元の文字列はどうなるのでしょうか?
str = "abc" puts str.upcase #=> ABC puts str #=> abc か ABC か、それが問題だ。
String#upcase
は大文字にした新しい文字列を返し、レシーバは変更されません。上のコードの最後の行は"abc"を出力します。しかし、新しい文字列を返すと言うことはオブジェクトの生成(メモリ確保して、GCに登録して、その他云々)というとても重い処理を行うことになります。元々の文字列自体が変更されてもかまわないのであれば、オブジェクトを再利用した方が速いです。そこでRubyでは元のオブジェクトを変えるString#upcase!
も用意しました。
Ruby
1str = "abc" 2str.upcase! 3puts str #=> ABC
String#upcase!
はレシーバそのものを大文字に変更します。新しいオブジェクトを生成するわけでは無いため、String#upcase
より高速です。1.9より前のとても重いRubyにとってはこの速度差は無視できないものでした。そのため、安全で副作用がないメソッドと危険だけど速く動くメソッドの二つを用意したのです。このようなレシーバに対して副作用がある(破壊してしまう)メソッドを破壊的メソッドと呼びます。破壊的メソッドという言葉はRubyコミュニティ独特の用語ですが、この概念は他の言語でも重要です。
通常であれば、安全なメソッドと破壊的メソッドの両方があるとプログラマーは混乱するでしょう。そこで、Rubyコミュニティでは、同じような動作をするメソッドが二つある場合は、より危険な方に"!"を付けるとしました。"!"マークで危険ですよと知らせると言うことです。もう気付いた方もいるかも知れませんが、「破壊的メソッドにはすべて"!"が付く」わけでも、「"!"が付いていれば破壊的メソッドである」わけでもありません。Array#push
は"!"が無いけど破壊的メソッドですし、Kernel.#exit!
は破壊的メソッドではありません(と言ってもプログラムが終了しますが)。"!"はあくまで通常より危険なメソッドと教えることが目的であり、破壊的変更であることが慣習的に自明なメソッド名(start, close, name=とか)や演算子(<<とか)の場合は付けません。
なお、破壊的メソッドと言っても、引数のオブジェクトを破壊的に変更してしまうようなメソッドのことではありません。そのようなメソッドは行儀が悪いメソッドであり、作るべきではないとされています。Rubyは参照の値渡しですので、引数へ副作用を与えることは可能ですが、コードが複雑になり、バグの温床になるため、余程の理由が無い限り避けるべきです。実際に、組み込みライブラリや標準ライブラリにそのようなメソッドはほとんどありません(というより、見つけられませんでした)。
"!"のついでに、似たような慣習として"?"を末尾に付けるがあります。真偽値を返すようなメソッドは"?"を付けるというものです。これは他言語も"isAbc"や"hasAbc"とすると慣習があったりするので、珍しいものではないと思います。
あと、もう一つ。Rubyは**「変数名の形や付いている記号で役割を区別する」**というとち狂った※特殊な言語文化を持っています。メソッド名の最後に"!"を付けるというのはRubyならではの発想であり、たぶん、他の言語では特殊すぎて採用はできないでしょう。Perl6で$!
というのを見たような気がしますが、まぁ、Larryだし。
※ 惰訳 - プログラムは大変だ、スクリプトにしよう - 過去篇 のRubyの項目参照。常にLarryはMatzより正しい。
投稿2016/03/20 12:53
編集2016/03/20 13:25総合スコア21737
0
(raccy さんの解説による、破壊的メソッドについて書きます)
Rust では、レシーバを変更するかどうかは、メソッド名の慣習ではなく、メソッドの型シグネチャで示します。コンパイラにより検査されますので、破壊的メソッドを、そうと知らずに使うことはできません。
例えば、String の push_str()
メソッドは、レシーバを変更します。
rust
1let mut s1 = String::from("Hello"); 2s1.push_str(", world!"); 3println!("{}", s1); // Hello, world! と表示される
このメソッドの型シグネチャは fn push_str(&mut self, string: &str)
となっており、レシーバ(&self
)に mut
が付いていることで、それが変更されることがわかります。
一方、to_uppercase()
メソッドは、レシーバを変更せず、新しい String オブジェクトを生成して返します。
rust
1let s1 = String::from("Hello"); 2let s2 = s1.to_uppercase(); 3println!("s1: {}, s2: {}", s1, s2); // s1: Hello, s2: HELLO と表示される
to_uppercase()
メソッドの型シグネチャは fn to_uppercase(&self) -> String
となっており、mut
が無いことで、レシーバが不変であることがわかります。
デフォルトが安全側の「変更しない」に倒されているのが、最近の言語っぽいところです。
Rust は(型推論付きの)静的型付け言語なので、型シグネチャは、コンパイラが検査します。最初の例で let mut s1 = ...
というように、変数 s1
にも mut
が付いていることに気づいたでしょうか? もし、これがないと、s1.push_str(", world!");
をコンパイルできません。
rust
1let s1 = String::from("Hello"); 2s1.push_str(", world!");
結果:コンパイルエラー
src/main.rs:6:9: 6:11 error: cannot borrow immutable local variable `s1` as mutable src/main.rs:6 s1.push_str(", world!"); ^~
これにより、push_str()
が破壊的メソッドであることを、うっかり知らずに使ってしまうことが防げます。
補足
上の例で、println!("{}", s1);
のように、!
が付いているものがありますが、その意味は、Ruby のそれとは全く異なります。これは、println!()
が、関数やメソッドではなくて、「マクロ」であることを示しています。(Rust では、可変長引数を取る関数やメソッドを定義できないため、println!()
がマクロとして定義されています)
投稿2016/03/20 15:20
編集2016/03/20 16:59総合スコア2044
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
Rubyには、メソッドに渡すオブジェクトを変更する破壊的メソッドには!を付ける慣習がありますよね。
ちょっと違います。
「同機能同名のメソッドで、一方が非破壊的、一方が破壊的である場合に、破壊的な方には!
を付ける」
です。
(厳密に言うと破壊的かどうかが違うし戻り値も違ってたりするので、「同機能」ではないので「ほぼ同機能」、!
が付いているので厳密に「同名」ではないので「!
を除くと同名」ということです)
!
の付いていない破壊的メソッドは山ほどあります。
他の多く言語では、(ほぼ)同機能でも(ほぼ)同名のメソッドを作らないのだと思います。
「(ほぼ)同機能のメソッドは、(ほぼ)同名にしたい」というのが「名前」にこだわるRuby流なのでしょう。
投稿2016/03/20 12:16
編集2016/03/20 12:19総合スコア85565
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
「破壊的メソッド」は聞き慣れない言葉だったので検索してみましたが、ほとんどがRuby関連ですね。Ruby特有の概念ということでしょうか。
C/C++では基本値渡しで、ポインタまたは参照でconstが付いていない場合はオブジェクトが書き換わる可能性がある、というように関数プロトタイプによって判断できますので、それが区別といえば区別になるかもしれません。別な言い方をすれば、C/C++では、引数に渡したオブジェクトを変更可能にしたい場合は、そのように記述する必要があるということになります。
C#は少々曖昧ですね。C#は構造体オブジェクトは値渡しですが、クラスオブジェクトはC++でいうところのポインタ渡しになるので、オブジェクトを変更することができてしまいます。にもかかわらず変更禁止にする仕組みが提供されていません。readonlyのようなそれっぽいキーワードはあるのですが、引数には使えません。ですので、C#では明確な区別はできません。かといって名前に何かを付けるといった慣習もありません。
追記
C#では引数で結果を受け渡しする際に、ref
やout
を付けて参照渡しにすることができます。その場合は書き換わることが明確に判断できます。
訂正
raccy さんの解説により、破壊的メソッドの意味を勘違いしていたことが判りました。
C++ではメソッドの後ろにconst
を付けることで、そのメソッドはオブジェクトを変更しないということを明示することができるので、少なくとも「変更しない」ことは判断が付きます。
投稿2016/03/20 10:11
編集2016/03/20 13:52総合スコア5942
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/03/20 10:34
2016/03/20 10:36
2016/03/20 13:56
0
こんにちは。
C++では渡された引数を変更しないことを強制できます。
void foo(string const& bar);
と宣言した関数fooは、文字列引数barを変更しませんと宣言しているので、もしも変更する可能性のあるコードがfooの中にあったらコンパイラがエラーにします。(色々ややこしい話もありますが、割愛)信頼性の高いソフトウェアを書くために使うことが推奨されます。
constは有り難い仕組みなのですが、メソッド呼び出し連鎖の結果、思わぬところに変更不可のコンパイル・エラーがでることがあり、意外にその修正に苦労します。
この経験上、処理系のサポートなくきちんと非破壊なメソッドを書くことはかなり難易度高そうです。
Rubyについてはよく知らないので事実かどうかよく分かりませんが、「破壊的メソッド」でググると面白い主張をしているQiitaのページがトップにでてきました。破壊的メソッド名には!をつけなければならない
投稿2016/03/20 09:26
編集2016/03/20 09:28総合スコア23272
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/03/20 13:18 編集
2016/03/20 13:23
2016/03/20 14:04
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/03/20 13:46