質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.39%
プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

Q&A

5回答

4504閲覧

Ruby以外の言語で、破壊的メソッド名への慣習はありますか?

Narugal

総合スコア24

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

0グッド

2クリップ

投稿2016/03/20 08:29

こんにちは。ふと気になり質問します。

Rubyには、メソッドに渡すオブジェクトを変更する破壊的メソッドには!を付ける慣習がありますよね。

他言語でそのような慣習は特に聞いたことないような気がしますし、じゃあ破壊的メソッドをどうやって区別してるの?と気になり質問します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答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
raccy

総合スコア21737

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

catsforepaw

2016/03/20 13:46

> Rubyの破壊的メソッドをよく知らない人へ どうやら破壊的メソッドの意味を勘違いしていたようです。解説ありがとうございます。
guest

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
tatsuya6502

総合スコア2044

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

Rubyには、メソッドに渡すオブジェクトを変更する破壊的メソッドには!を付ける慣習がありますよね。

ちょっと違います。
「同機能同名のメソッドで、一方が非破壊的、一方が破壊的である場合に、破壊的な方には!を付ける」
です。
(厳密に言うと破壊的かどうかが違うし戻り値も違ってたりするので、「同機能」ではないので「ほぼ同機能」、!が付いているので厳密に「同名」ではないので「!を除くと同名」ということです)

!の付いていない破壊的メソッドは山ほどあります。

他の多く言語では、(ほぼ)同機能でも(ほぼ)同名のメソッドを作らないのだと思います。
「(ほぼ)同機能のメソッドは、(ほぼ)同名にしたい」というのが「名前」にこだわるRuby流なのでしょう。

投稿2016/03/20 12:16

編集2016/03/20 12:19
otn

総合スコア85565

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

「破壊的メソッド」は聞き慣れない言葉だったので検索してみましたが、ほとんどがRuby関連ですね。Ruby特有の概念ということでしょうか。

C/C++では基本値渡しで、ポインタまたは参照でconstが付いていない場合はオブジェクトが書き換わる可能性がある、というように関数プロトタイプによって判断できますので、それが区別といえば区別になるかもしれません。別な言い方をすれば、C/C++では、引数に渡したオブジェクトを変更可能にしたい場合は、そのように記述する必要があるということになります。

C#は少々曖昧ですね。C#は構造体オブジェクトは値渡しですが、クラスオブジェクトはC++でいうところのポインタ渡しになるので、オブジェクトを変更することができてしまいます。にもかかわらず変更禁止にする仕組みが提供されていません。readonlyのようなそれっぽいキーワードはあるのですが、引数には使えません。ですので、C#では明確な区別はできません。かといって名前に何かを付けるといった慣習もありません。


追記
C#では引数で結果を受け渡しする際に、refoutを付けて参照渡しにすることができます。その場合は書き換わることが明確に判断できます。


訂正
raccy さんの解説により、破壊的メソッドの意味を勘違いしていたことが判りました。
C++ではメソッドの後ろにconstを付けることで、そのメソッドはオブジェクトを変更しないということを明示することができるので、少なくとも「変更しない」ことは判断が付きます。

投稿2016/03/20 10:11

編集2016/03/20 13:52
catsforepaw

総合スコア5942

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ozwk

2016/03/20 10:20

C#でオブジェクトが変更可能かどうかは、そのオブジェクトの関心事項であり、 引数にとる側が気にすることではないということなんでしょうね
catsforepaw

2016/03/20 10:34

なるほど、そういう考えもあるかもしれません。ただ、C++出身の者としては、引数のアクセス権がコントロールできないことには若干の不満が残ります。out引数にする際は、ref/outを使いつつ自分ルールで引数名で判るようにはしてますが。
catsforepaw

2016/03/20 10:36

↑のコメントを書いて気づいたので回答に追記します。
catsforepaw

2016/03/20 13:56

Rubyの破壊的メソッドを良く理解しないまま書いていたので訂正を追加しました。 (質問を読んだらそう思っちゃいますよね? ということで勘弁してください。)
guest

0

こんにちは。

C++では渡された引数を変更しないことを強制できます。
void foo(string const& bar);と宣言した関数fooは、文字列引数barを変更しませんと宣言しているので、もしも変更する可能性のあるコードがfooの中にあったらコンパイラがエラーにします。(色々ややこしい話もありますが、割愛)信頼性の高いソフトウェアを書くために使うことが推奨されます。

constは有り難い仕組みなのですが、メソッド呼び出し連鎖の結果、思わぬところに変更不可のコンパイル・エラーがでることがあり、意外にその修正に苦労します。
この経験上、処理系のサポートなくきちんと非破壊なメソッドを書くことはかなり難易度高そうです。

Rubyについてはよく知らないので事実かどうかよく分かりませんが、「破壊的メソッド」でググると面白い主張をしているQiitaのページがトップにでてきました。破壊的メソッド名には!をつけなければならない

投稿2016/03/20 09:26

編集2016/03/20 09:28
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

raccy

2016/03/20 13:18 編集

破壊的メソッドは、引数に対する破壊的変更があるかどうかではなく、レシーバに対する破壊的変更があるかどうかです。C++でいえば、class Tのメンバー関数で`T&& foo(string consnt& bar) const;`などと関数自体にconstがあるかどうかというイメージだと思っていただければと思います。最も代表的なのはgetterとsetterです。getterは非破壊的ですが、setterは破壊的です。引数に対して副作用があるかどうかはまた別の問題なります。
raccy

2016/03/20 13:23

あ、その前に、質問者が勘違いしていますね。失礼しました。
Chironian

2016/03/20 14:04

raccyさん。 解説ありがとうです。Rubyはよく知らないので助かります。 ところで、レシーバという表現初めて聞いた気がします。調べたところ、メッセージの受け手側のオブジェクトのことなのですね。オブジェクト指向らしい分かりやすい表現ですね。 何分、ベターC上がりのC++erなのでオブジェクト指向はおまけなのですよ。(笑) こんな時に実感してしまいます。orz
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.39%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問