「他言語からのRuby入門」をやってみる
6時間かかった。
- 情報源
- 方針
- 重要な言語機能と知っておくべきこと
- イテレーション
- すべては値(式と文に違いなし)
- シンボル(object_id)
- すべてはオブジェクト
- 可変な定数
- 名前付けの規約
- キーワード引数
- 普遍の真理(nilとfalse以外は真)
- アクセス修飾子はスコープの最後まで適用される
- アクセス制御(private,public,protected)
- クラスは開いている(いつでも定義変更できてしまう)
- 不思議なメソッド名(!, ?)
- 特異メソッド(特定インスタンスしか持っていない)
- 「存在しなかった」メソッド(method_missing)
- 関数呼び出しではなくメッセージ送信
- ブロックは作りかけのオブジェクト
- 演算子は糖衣構文(シンタックスシュガー)
- 所感
- 対象環境
情報源
方針
他言語からのRuby入門を読んでirb
で触ってみる。
言語別のリンクはとりあえず放置。まずは「重要な言語機能と知っておくべきこと」だけやる。
重要な言語機能と知っておくべきこと
17個ある。
- イテレーション
- すべては値(式と文に違いなし)
- シンボル(
object_id
) - すべてはオブジェクト
- 可変な定数
- 名前付けの規約
- キーワード引数
- 普遍の真理(
nil
とfalse
以外は真) - アクセス修飾子はスコープの最後まで適用される
- アクセス制御(
private
,public
,protected
) - クラスは開いている(いつでも定義変更できてしまう)
- 不思議なメソッド名(
!
,?
) - 特異メソッド(特定インスタンスしか持っていない)
- 「存在しなかった」メソッド(
method_missing
) - 関数呼び出しではなくメッセージ送信
- ブロックは作りかけのオブジェクト
- 演算子は糖衣構文(シンタックスシュガー)
イテレーション
[1,2,3].each do |i| puts "#{i}" end
1 2 3 => [1, 2, 3]
出てきたキーワードはeach
,collect
,find
,inject
,sort
。メソッドだろう。
ri Enumerable
を参照しろとあった。CLIでドキュメントが見れるらしい。
ri Enumerable
= Enumerable (from ruby core) ------------------------------------------------------------------------ The Enumerable mixin provides collection classes with several traversal and searching methods, and with the ability to sort. The class must provide a method #each, which yields successive members of the collection. If Enumerable#max, #min, or #sort is used, the objects in the collection must also implement a meaningful <=> operator, as these methods rely on an ordering between members of the collection.
列挙可能なミックスインは、コレクションクラスにいくつかのトラバーサルを提供します と検索方法、そしてソートする機能を備えています。 クラスはしなければなりません メソッド#eachを提供します。これにより、 コレクション。 Enumerable#max、#min、または#sortが使用されている場合、 コレクションには、意味のある<=>演算子も実装する必要があります。 メソッドは、コレクションのメンバー間の順序に依存します。
よくわからん。ようするにループする仕組みがeach
ってことね。ほかの言語にもメソッドとしてForEach
みたいなのがあったりするし。同じだろう。
個人的には1行で書けることが大事。内容が2行以上のときはセミコロン;
でつなげばいい。
字数が少ないためか見やすい。これはBashよりもいい感じだ。すばらしい!
[1,2,3].each do |i| print 'V='; puts "#{i}"; end
V=1 V=2 V=3 => [1, 2, 3]
メソッド | 改行 |
---|---|
puts |
あり |
print |
なし |
すべては値(式と文に違いなし)
Rubyは式と文の違いがない。すべて値をもっている。if
でさえ値を返す。
x = if true 1 else 2 end puts x
1
これは衝撃。私がしっているC言語やPythonなどのif文は文なので値を返さない。返すためには関数化してreturn
せねばならなかった。
あるいはC#のマッチ式は式なので値を返した。制御しつつ値を返す組込構文はマッチ式しか知らなかった。Rubyではすべてが値だという。つまり他の言語では文だったほかのfor
などの制御文も値を返すということだろう。たしかにイテレータの項ではeach
の結果として配列が返されていた。つまり、すべては値というか、すべては式であり値を返すということだろうか。
すごく便利そう。
シンボル(object_id
)
ドキュメントにはいきなり以下のように書いてあった。
:george.object_id == :george.object_id
説明不足すぎる。まずシンボルってなに? たぶん名前の先頭に:
がついているやつがシンボルなのだろう。シンボルってどうやってつくるの? 適当に触っていたら以下のように作れたっぽい。
irb(main):012:0> s = :my_symbol => :my_symbol irb(main):013:0> s.object_id => 1322900
おそらくシンボルは以下のような書式で書くのだろう。そのインスタンスが返されるので、変数で受け取る、と。
:[a-zA-Z_]
シンボルの用途は識別子だろう。同一の名前:my_symbol
はいつも必ず同じIDをもつ。試しに2個目の:my_symbol
を作って別の変数に入れてみよう。
irb(main):014:0> t = :my_symbol => :my_symbol irb(main):015:0> t.object_id => 1322900 irb(main):016:0> s.object_id == t.object_id => true
え、それって当たり前のことなんじゃないの? と思うかも知れないが違う。インスタンス生成されたら、それぞれのメモリは別に確保される。それを同じインスタンスとは判定しない。
試しに文字列リテラルのobject_id
を取得してみる。値は同じs
だが、IDは別物だ。
irb(main):017:0> 's'.object_id => 156 irb(main):018:0> 's'.object_id => 168 irb(main):019:0> 's'.object_id => 180
インスタンスは別だが、値が同じだったら同一であると判断したい場合もある。そういったときにシンボルが使えるのだと思う。
すべてはオブジェクト
先述のように、文字列リテラルもString型のインスタンスなのだろう。さらにいえばStringというクラス(型)もオブジェクトなのだろう。
以下は同じ意味なんだってさ。
class MyClass attr_accessor :instance_var end
MyClass = Class.new do attr_accessor :instance_var end
ところでオブジェクトってなんなの? すべてがオブジェクトだったら、オブジェクトが何であるかわからないと思う。オブジェクト以外のものがないと比較できない。演算子もオブジェクトなの? たぶんそうなのだろう。
文脈からしてオブジェクトとはインスタンスのことを言っているように思う。ユーザがクラスからClass.new
で生成して返されたもの以外も含めてオブジェクトと呼んでいるのだろうか。たとえば's'
のように文字列リテラルを書いたらStringインスタンスが返される。それも含めて区別せずオブジェクトと呼んでいるのだろう。
オブジェクトはインスタンス変数やメソッドを持っているものだと思う。たぶんRubyの全要素は何らかの変数やメソッドを持っているのだろう。知らんけど。
オブジェクトについての説明がなさすぎて、他の言語を参考に根拠なき憶測をするしかない。これ以上はRubyの言語仕様やソースコードを読むことになるのだろう。そんなことはしたくないのでスルーする。
可変な定数
定数は厳密な意味での定数ではありません。 初期化済みの定数を変更したとしても、 警告はされますが、プログラムは停止しません。 だからと言って、定数を再定義するべきだということではありません。
可変な定数
というだけですでに自己矛盾しているので嫌な予感はしていた。
初期化済みの定数を変更したとしても、 警告はされますが、プログラムは停止しません。
「定数を変更」って、それただの変数じゃん。変更できないから定数なはずでしょ?
Rubyは定数を作れない。そういうことでOKだよね? コード例もないし。もしC言語ならconst int PI = 3.14
とか書いていたはずだし。
どうして、さも定数という概念があるかのように書くの? めちゃくちゃわかりづらい文章だし。
名前付けの規約
例 | ルール | 種別 |
---|---|---|
Const ,CONST ,ConsT |
大文字ではじまる | 定数 |
$global |
$ ではじまる |
グローバル変数 |
@name |
@ ではじまる |
インスタンス変数 |
@@num |
@@ ではじまる |
クラス変数 |
ただ、メソッド名についてのルールは書いてなかった。その代わり、メソッド名は大文字ではじめることもできるから次のように紛らわしい書き方ができてしまうとあった。いや、だったらメソッド名のルールも決めてくれよ。
Constant = 10 def Constant 11 end
> puts Constant 10 > puts Constant() 11
多すぎる。なのに命名規則が見当たらない。
以下にまとめてみた。
要素 | 方法 | 品詞 | 例 |
---|---|---|---|
class ,module |
CamelCase | 名詞 | MyClass |
パス | snake_case | ? | dir_name/my_class.rb |
メソッド | snake_case | 動詞 | do_action |
変数 | snake_case | 名詞 | first_name |
定数 | SCREAMING_SNAKE_CASE | 名詞 | MAX_SIZE |
真偽値を返すメソッド | snake_case? | 補語(名詞,形容詞)? | has? ,can? ,is? ,equal? |
破壊的メソッド | snake_case! | 動詞 | sort! ,replace! |
品詞については言及されていなかった。たぶん常識だからだろう。でも英語がわからない私にはそこもヒントが欲しい。
キーワード引数
キー:値
とすることでキーワード引数にできる。キーワードに名前をつけて指定できる。位置が違っても名前が一致すればOK。
def say(name:'山田', age:10) "#{name}は#{age}歳です。" end
puts say puts say name:'鈴木' puts say age:99 puts say name:'鈴木', age:99 puts say age:99, name:'鈴木'
> puts say 山田は10歳です。 > puts say name:'鈴木' 鈴木は10歳です。 > puts say age:99 山田は99歳です。 > puts say name:'鈴木', age:99 鈴木は99歳です。 > puts say age:99, name:'鈴木' 鈴木は99歳です。
ただし名前を指定せず位置引数のようにするとエラーになってしまう。融通が効かない。
> say '山田', 22 (irb):50:in `say': wrong number of arguments (given 2, expected 0) (ArgumentError)
もし値がなければ名前付きの必須位置引数になる。
def say(name:, age:10) "#{name}は#{age}歳です。" end
> puts say (irb):40:in `say': missing keyword: :name (ArgumentError)
> puts say '太郎', 99 (irb):40:in `say': wrong number of arguments (given 2, expected 0; required keyword: name) (ArgumentError)
name:値
として渡してやる必要がある。
> say name:'太郎' => "太郎は10歳です。" > say name:'太郎', age:99 => "太郎は99歳です。"
可変長引数、オプション引数
スタイルガイドをざっと見ていたらオプション引数というのを見かけた。え、それってキーワード引数とは別物なの? 調べてみたら**options
というのがあるらしい。任意のキーワードと値を渡せるやつだ。
Pythonにもあったはずだ。大抵は可変長引数も一緒になっていて*args, **kwargs
のようになっていたはず。調べてみるとRubyには可変長引数もあるっぽい。
def some(*args, **kwargs) puts "#{args}" puts "#{kwargs}" end
> some *[1,3,5], **{'key1':'v1', 'key2':'v2'} [1, 3, 5] {:key1=>"v1", :key2=>"v2"} => nil
配列や辞書の展開方法も同じっぽい。
こういうことはまとめて説明してほしかった。
普遍の真理(nil
とfalse
以外は真)
Rubyでは、
nil
とfalse
を除くすべてのものは真と評価されます。 CやPythonを始めとする多くの言語では、0
あるいはその他の値、空のリストなどは 偽と評価されます。
if 0 puts '真' else puts '偽' end
真
0
は真。な、なんだってー。C言語からの慣習をぶちこわしたな。でもポストNULL
のnil
は偽なのか。そのほうが直感的、なのかな? とにかく「nil
とfalse
以外は真」と覚えるしかない。
アクセス修飾子はスコープの最後まで適用される
class MyClass def public_method_1; true end private def private_method_1; true; end def private_method_2; false; end end
え、アクセス修飾子があるんだ? まずはそこから説明してくれよ。クラスがあって、アクセス修飾子があるって説明から先にしてほしかった。
あと、メソッドを1行で定義するときって、メソッド名の後ろに;
つけるのかよ。まあ{}
で囲まないからね。仕方ない、のかね? だったらやっぱりend
って冗長に思えてしまう。
話を戻す。つまりアクセス修飾子private
と書いたら、それ以降に書かれたメソッドはすべてprivate
になるってことね? たしかC++がそんな感じだったよね。
class MyClass { private: void do1(); void do2(); };
アクセス制御(private
,public
,protected
)
アクセス修飾子 | 概要 |
---|---|
public |
すべてに公開される |
private |
レシーバなしで呼び出すことができる場合に、そのメソッドへアクセス可能となります。 つまり、selfのみがprivateメソッドを呼び出す際のレシーバとなります。 |
protected |
クラスか継承関係にあるクラスのインスタンスからのみ 呼び出すことができます。しかしまた、 レシーバとしてインスタンスを指定しても呼び出すことができてしまいます。 |
先生、レシーバ
ってなんすか? 説明してくれよ。文脈からしてインスタンス変数のことみたいだが。
その前に、基礎から確認したい。
public(省略)
class C def m; puts 'public method'; end end
> C.new.m public method
public(明記)
class C public def m; puts 'public method'; end end
> C.new.m public method
private
class C private def m; puts 'private method'; end end
> C.new.m (irb):92:in `<main>': private method `m' called for #<C:0x018b58c0> (NoMethodError)
private(selfから呼ぶ)
class C def n; self.m; end private def m; puts 'private method'; end end
> C.new.m (irb):108:in `<main>': private method `m' called for #<C:0x018c6300> (NoMethodError) > C.new.n private method
今更だけどPythonみたく引数にself
を書かなくていいのは素敵。まあPythonがクソ💩なだけだけど。
protected
class C protected def m; puts 'private method'; end end
> C.new.m (irb):102:in `<main>': protected method `m' called for #<C:0x018faf80> (NoMethodError)
protected(子が親のメソッドを参照する)
class C protected def m; puts 'private method'; end end class D < C def n; self.m; end end
> D.new.n private method > D.new.m (irb):118:in `<main>': protected method `m' called for #<D:0x01cd54b8> (NoMethodError)
まず継承について。class 自分 < 親
とする。Pythonならclass 自分(親)
だし、C#ならclass 自分 : 親
だった。
子クラスD
は、親クラスC
のprotected
メソッドをself
で参照できる。子クラスの定義内では参照できるが、子クラスのインスタンスからは参照できない。うん、思ったとおりのprotected
な動作だ。
レシーバとしてインスタンスを指定すれば呼び出せてしまう
は? 何言ってるかわかんね。ひとつずつ見てゆく。
class Test def identifier; 99; end def ==(other); identifier == other.identifier; end end
def ==(other)
ってなんだよ。他言語の経験とコードから察するに、演算子のオーバーロードだろう。インスタンス変数に==
演算子が使われたときにこの処理が実行されるよう定義したのだと思う。
> t1 = Test.new => #<Test:0x01cdc3b8> > t2 = Test.new => #<Test:0x01c08af8> > t1 == t2 => true
t1
とt2
はそれぞれ異なるインスタンスである。それらをt1 == t2
で比較している。このときdef ==(other)
の処理が呼ばれる。その中身は99
と99
の等価比較した結果だ。当然true
である。
次に99
を返すメソッドをpublic
からprotected
へ変更する。
class Test protected :identifier end
そして==
演算子をオーバーロードしたメソッドからそいつを参照する。
t1 == t2 # => true
はい、参照できちゃいました。定義されているコードをみるとother.identifier
となっている。other
というレシーバがprotected
なメソッドidentifier
を呼んでいる。こうしたらprotected
でも呼び出せますよって話なんだと思う。
え、べつに不思議はいよね? だって自分のクラスで定義したメソッド内で呼び出しているんだし。private
なメソッドだって呼び出せるよね? あれ、でもprivate
はself
からしか呼べないんだっけ?
試してみよう。
class Test private :identifier end
> t1 == t2 (irb):121:in `==': private method `identifier' called for #<Test:0x01c08af8> (NoMethodError)
はい、エラーですね。たとえother
というレシーバがあっても、private
はself
というレシーバからしか呼べないからエラーになったということだろう。
ところで、==
の左辺と右辺でどちらもidentifier
を呼んでいるけど、一体どちらでエラーになったの? 両方?
def ==(other); identifier == other.identifier; end
じつは左辺のやつがself
ついていないからエラーでした、とかいうオチだったりしない? 本当にother
で参照しているのが原因? 確かめてみよう。
identifier
を呼び出すだけのpublic
なメソッドcall
を追加してみる。
class Test def call; identifier; end end
呼び出す。使えた。つまりself
は省略できるんだね。アクセス修飾子がなんであれ同一名のメソッドがあればself
がついているものとみなして参照してくれるみたい。
> t1.call
=> 99
ということはやはり、other
からはprivate
が参照できないせいでエラーになっていたようだ。
念のため、おなじようにメソッドを追加して確かめてみる。
class Test def call_other(other); other.identifier; end end
> t1.call_other(t1) (irb):143:in `call_other': private method `identifier' called for #<Test:0x01cdc3b8> (NoMethodError)
はいエラーですね。ようするに引数で渡されたインスタンス変数からはprivate
なメソッドを参照できないと。
でも、protected
なら参照できちゃうよって話が本題ね。だから気をつけてね、と。
再びidentifier
をprotected
にして確かめてみよう。
class Test protected :identifier end
> t1.call => 99 > t1.call_other(t1) => 99 > t1.identifier (irb):19:in `<main>': protected method `identifier' called for #<Test:0x0172c050> (NoMethodError)
protected
なのでインスタンス変数から直接呼び出すことはできない。なのでt1.identifier
はエラーになる。
なのに、t1.call_other(t1)
は成功してしまう。呼び出せないはずのprotected
なメソッドでも、インスタンス変数を引数で渡して、受け取ったメソッド内で呼び出すことはできてしまう。
……なるほど? でもさ「気をつけろ」っていうけど、何に気をつければいいんだ? べつに問題なくね? 自分のクラス内で呼び出す分にはちゃんとテストするだろうし問題ないだろう。
うーん、気をつけるべきことがわからん。まあとにかく、protected
なメソッドでもインスタンス変数を渡してそのクラス内で呼び出すことはできちゃうってことで。
アクセス修飾子 | 許容するレシーバ | インスタンス変数からの参照 |
---|---|---|
public |
すべて | ⭕ |
protected |
すべて | ❌ |
private |
self のみ |
❌ |
クラスは開いている(いつでも定義変更できてしまう)
いつでもクラスを開いて、定義を足したり、変更することができます。
もうすでに試してみた。ダッグタイピングらしい。恐すぎるよ。どこでバグになったかわかりづらくなっちゃう。
Integerや、すべてのオブジェクトの祖先であるObjectのようなクラスであっても、 自由に再定義することが可能です。
自由すぎてヤバイ。Pythonよりも自由。うっかり名前重複しても気づかずに既存のクラスを魔改造してしまうことになる。
class Integer def hours self * 3600 # 1時間あたりの秒数を返します end alias hour hours end
> 1.hours => 3600 > 2.hour => 7200
Pythonでやりたかったけどできなかったことが、Rubyならできる! いいね😀
怖いけど便利。どっちがいいんだろう。
alias
っていう文?もあるのか。新しいほうの名前から先に書くの? 違和感ある。
不思議なメソッド名(!
, ?
)
命名規則でやった。真偽値を返すメソッドは名前の末尾に?
をつける。破壊的メソッドは名前の後ろに!
をつける。
でもすべてそのルールに従っているわけではないらしい。Array#replace
は引数の配列を書き換えるのに!
がない。
じゃあもう不思議っていうより、ちゃんと命名規則に従わずに中途半端な状態ですってことじゃん。そういうのってC#みたく引数にref
とか書いてくれたらいいのに。
すべては値を返す。Array#replace
は何を返す? 引数の配列をさすポインタ? それともコピーした別のメモリ? 説明どおりならポインタを返すはず。決してコピーされて新しい配列は返さないはずだ。それを確かめてみよう。
a = [1,2] b = [3,4] a.object_id b.object_id c = a.replace(b) # a の内容を b の内容へ置き換える a.object_id b.object_id c.object_id a == [3,4] && b == [3,4] && c == [3,4] a.object_id != b.object_id a.object_id == c.object_id
> a = [1,2] => [1, 2] > b = [3,4] => [3, 4] > a.object_id => 240 > b.object_id => 252 > c = a.replace(b) # a の内容を b の内容へ置き換える。aのポインタを返す => [3, 4] > a.object_id => 240 > b.object_id => 252 > c.object_id => 240 > a == [3,4] && b == [3,4] && c == [3,4] => true > a.object_id != b.object_id > a.object_id == c.object_id # a と c はおなじメモリを指している => true
replace
で置き換えるa
と、replace
の戻り値c
は、おなじobject_id
だった。なので同じインスタンスである。コピーして新しいインスタンスを作ったわけではない。うん、説明どおりっぽいね。Array#replace
は配列の中身を引数で渡された値で書き換える破壊的メソッドですよと。なのに名前の末尾に!
がついていない。
命名規則が守られないことがある。でもたぶんreplace
はその名前からしてもう書き換えることがわかりきっているから、べつにいいよね? むしろ名前を短くするためにも!
がないほうがいいよね? ということなのだろう。たぶん。
自分が新たに破壊的メソッドをつくるときはどうしたらいいんだろうね? 都度決めるしかないのかな? そもそも私としては名前に?
や!
が使えちゃうのが衝撃だけど。やっぱC#のref
がよくできていたと思うわ。名前だけでなく動作と紐付いた表現だし。
特異メソッド(特定インスタンスしか持っていない)
特異メソッドとは、特定のインスタンスに対してのみ既存メソッドをオーバーライドすること。だと思う。
class C; def m; puts'メソッド'; end end c = C.new d = C.new c.m d.m def d.m; puts '特異メソッド'; end c.m d.m
> class C; def m; puts'メソッド'; end end => :m > c = C.new => #<C:0x01771d28> > d = C.new => #<C:0x0181c410> > c.m メソッド => nil > d.m メソッド => nil > def d.m; puts '特異メソッド'; end => :m > c.m > d.m メソッド 特異メソッド => nil
ちなみに存在しないメソッドを特異メソッドにしようとすると以下エラーになる。
def d.x; puts '特異メソッド'; end undefined method `x' for #<C:0x0177f450> (NoMethodError)
ドキュメントの説明だけだと、存在しないメソッドも追加できると思ったが、できなかった。説明するならちゃんとしてくれ。
「存在しなかった」メソッド(method_missing
)
Rubyはメッセージに対応するメソッドを見つけられなかったとしても諦めません。 その場合は、見つけられなかったメソッド名と引数と共に、 method_missingメソッドを呼び出します。 method_missingメソッドはデフォルトではNameError例外を投げますが、 アプリケーションに合うように再定義することもできます。 実際、多くのライブラリがそのようにしています。 以下がその例です。
def method_missing(id, *arguments) puts "Method #{id} was called, but not found. It has " + "these arguments: #{arguments.join(", ")}" end
なんか、めっちゃ怒られたんですけど。method_missing
の再定義ってirb
ではできないのかな?
(irb):1: warning: redefining Object#method_missing may cause infinite loop Method to_ary was called, but not found. It has these arguments: Method to_ary was called, but not found. It has these arguments: Method to_hash was called, but not found. It has these arguments: Method to_hash was called, but not found. It has these arguments: => :method_missing Method to_str was called, but not found. It has these arguments: Method to_io was called, but not found. It has these arguments: Method to_io was called, but not found. It has these arguments: Method to_io was called, but not found. It has these arguments: Method to_ary was called, but not found. It has these arguments: Method to_ary was called, but not found. It has these arguments: Method to_ary was called, but not found. It has these arguments: irb(main):005:0> Method to_ary was called, but not found. It has these arguments:
仕方ないからファイルを作ろう。
a.rb
#!/usr/bin/env ruby
__
$ vim a.rb $ chmod +x a.rb $ ./a.rb ./a.rb:2:in `<main>': undefined local variable or method `__' for main:Object (NameError)
__
というメソッドは未定義ですよ、というNameError
が出る。これがデフォルトの動作。
ここでmethod_missing
を再定義してみる。
a.rb
#!/usr/bin/env ruby def method_missing(id, *arguments) puts "Method #{id} was called, but not found. It has " + "these arguments: #{arguments.join(", ")}" end __
./a.rb
./a.rb:2: warning: redefining Object#method_missing may cause infinite loop Method __ was called, but not found. It has these arguments:
ええ、再定義したことが警告として出るのかい。これ、本当に多くのライブラリが使っているのか? それよりtry
,catch
構文みたいなのないの?
関数呼び出しではなくメッセージ送信
メソッド呼び出しは実際には他のオブジェクトへのメッセージ送信です。
# これは 1 + 2 # これと同じで... 1.+(2) # つまりこういうことです。 1.send "+", 2
昔オブジェクト指向の説明でこんな感じの話をみた気がする。よくわからん。
irb
で実行してみると全部成功した。
> 1+2 => 3 > 1.+(2) => 3 > 1.send "+", 2 => 3
マジか。ええと、つまり数値リテラル1
はInteger
クラス型のインスタンスで、send
メソッドを持っている。その引数として演算子+
と値2
を受け取って計算結果の3
を返した。そういうことだよね? 構文から読み取ったらそういうことになる。ふーん、そうなんだ。send
なんてメソッドがあるんだね。
1.+(2)
がわからん。.
があるということは+
はメソッドか。ああ、演算子オーバーロードか。()
がついているのはメソッドであり、その引数に()
内で2
を渡していると。なるほど、だいたいわかった。なんか読めるようになってきたかも。でも絶対にこんなコードは書かないと思う。
ブロックは作りかけのオブジェクト
ブロック(実際にはクロージャ)は標準ライブラリでもすごく使われています。 ブロックを呼び出すには、yieldを使うか、引数リストに特別な引数を追加して それをProcオブジェクトにします。以下がその例です。
は? ブロックってなに? クロージャってなに? yield
? Proc
? 引数名の頭にある&
ってなに? さっぱりわからんのですが。急にぶっこみすぎだろ。説明なさすぎでしょ。
def block(&the_block) # この内側では、the_blockはメソッドに渡されたブロックになります。 the_block # ブロックが返ります end adder = block { |a, b| a + b } # adderはここでProcオブジェクトになります adder.class # => Proc
ええと、たぶん文脈からして{ |a, b| a + b }
というのがブロックなのだろう。block
というシンボルはメソッド名として定義されている。それを呼び出して{ |a, b| a + b }
を渡しているようにみえる。ということは引数の&the_block
の中身は{ |a, b| a + b }
なのだろう。おそらくその中身はメソッド。|a,b|
は引数で、a + b
は式。そしてたぶんその結果をreturn
している。ふつうにメソッドを書いてもreturn
と明記せずともreturn
になるのがRubyだもんね。
ははーん、さてはPythonでいうラムダ式のことだな? C#でいう匿名メソッド。そういえばJavaScriptではそれをクロージャっていうんだっけ? クロージャはよく聞くけど理解していない。
&
はなんだろう。C言語でいうとポインタアドレス変数を意味するはず。たぶんここでは関数ポインタを意味するのだろう。そしてRubyでは関数ポインタがあるかどうかは知らないが、Proc
というオブジェクトによって無名のメソッドを定義できる。そして関数の引数として渡せる。C言語では関数のポインタを取得することで引数として渡すことができる。その仕組みをProc
オブジェクトと&
で表現しているのがRubyってことなのかな?
Proc
ってたぶんプロシージャのことだよね? 処理の単位。やっぱり情報不足。ちょっとググってみた。
p = Proc.new {|s| s.to_s} [:aaa, :bbb, :ccc].map(&p) #=> ["aaa", "bbb", "ccc"]
{|s| s.to_s}
は引数s
のもつto_s
メソッドを実行するという意味だろう。to_s
は文字列化。String
を返す。
map()
は配列の要素ひとつずつに対して与えられた処理をするメソッド。ここではその処理をProc
で与えている。つまり配列要素をすべて文字列化する処理になる。配列の要素はシンボル。つまりシンボルを文字列に変える処理だ。
以下のような書き方もできるらしい。なんじゃこりゃ。&
はProc
をブロックとして使うために書くとして。to_s
は処理の内容。では、そのメソッドを呼び出すレシーバは? 書いてないよ? &
とto_s
の間に:
があるけど、そいつがレシーバを示しているってことなのかな?
[:aaa, :bbb, :ccc].map(&:to_s) #=> ["aaa", "bbb", "ccc"]
よくわからんけど、今はいいや。
ちなみにPythonでは似たようなことを次のように書く。以下は文字列を数値化している例。
list(map(lambda x: int(x), ['1','2','3'])) #=> [1, 2, 3]
Proc.newにブロックを渡すか、lambdaメソッドを呼び出すことで、 メソッド呼び出しの外側でブロックを作成することもできます。
コード例を出してくれよ。
同様に、メソッドもまた作りかけのオブジェクトです。
method(:puts).call "putsはオブジェクト!" # => putsはオブジェクト!
なにと同様なの? 「作りかけのオブジェクト」ってなに? 壊れかけのレディオみたいなもん?
method
ってなんだよ。たしかに動いたけど、説明なさすぎィ!
> method(:puts).call "putsはオブジェクト!" putsはオブジェクト!
なんか、急に説明が雑になったな。今までも説明不足だったが、輪をかけて説明不足だよ。もう何言ってんのか本当にわかんない。よくここまで深読みしたよ私。もうゴールしていいよね? ギブアップしていいよね? 「空気読めよ」っていわれたときと同じくらい理不尽だよ。わかんないよ。自分の口で説明してくれよ。こっちは知りたくてドキュメント読んでいるんだからさあ。
演算子は糖衣構文(シンタックスシュガー)
Rubyにおけるほとんどの演算子は糖衣構文です。 いくつかの優先順位規則にもとづいて、メソッド呼び出しを単に書き換えているだけです。 たとえば、Integerクラスの+メソッドを次のようにオーバーライドすることもできます。
class Integer # できるけれど、しないほうがいいでしょう def +(other) self - other end end
うわぁ、これはひどい。できちゃうんだね、こういうことが。そう、Rubyならね。
C++のoperator+などは必要ありません。
や=といったメソッドを定義すれば、配列のようなスタイルでアクセスすることもできます。 (+1や-2みたいな)単項の+や-を定義するには、+@か-@といったメソッドを定義する必要があります。 けれど、以下の演算子は糖衣構文ではありません。 これらはメソッドではないので、再定義できません。
=, .., ..., not, &&, and, ||, or, ::
加えて+=、*=などはvar = var + other_var、var = var * other_var などの処理の略記法になり、これらも再定義できません。
思ったんだけどインクリメント演算子ないの? C/C++ではx++
とか++x
とやれば+1
できたのに。
++の動作が本質的に「変数を操作する」ものであるため,変数がオブジェクトでないRubyでは導入できないでいます.++や--の「オブジェクト指向的意味」がRubyの他の部分と整合性を保ったまま定義できれば採用したいのですが….
いやいや、変数がオブジェクトでないRubyでは
って、嘘でしょ? Rubyはすべてオブジェクトじゃなかったんかーい!
どうやらURLの人は数値インスタンスはimmutableで変更不可なせいではないかと言っている。
よくわからない。だってa += 1
はできるじゃん。それだって値を変更するものじゃないか。なのにできるよ? だったらa++
だってできてもいいのでは? +=1
がよくて++
がダメな理由がわからない。
++
は値を変更するmutableなもの。それをimmutableな実装でやろうとすると別のインスタンスを生成することになる。それはもう以前の変数が参照している実体とは違うわけで。古い数値はどうするのかといえば、たぶんGCで削除されるのを待つことになるはず。そして新しいインスタンスを返して変数に代入する。これで完了。たぶんRubyの数値型がimmutableなら+=1
はそうなっているんじゃないの? それが動いているなら++
だってできるのでは? 演算子オーバーロードでやればできるんじゃないのか。
Rubyの実装を理解していないと判断のしようがない。そこまで深く掘り下げるつもりはない。++
って超便利なんだけど、使えない言語は結構ある。BashやPythonも使えない。+=
は冗長だし=
や==
と見分けがつきにくい。だから++
や--
がほしい。でもa++
と++a
の違いとかあるし、たまに罠になることがある。実装が大変なのかもね。
a = 1 # OK a += 1 # OK a++ # NG
所感
説明不足感がある。でも、無駄がないためサクッと概要を把握できたのは嬉しい。Pythonのドキュメントはごちゃごちゃしているくせに知りたいことがわからなかったりする。サクッと概要を把握したいときはノイズばかりで、ちゃんと把握したいときは不足。そんなことになるくらいだったら、このくらいでいいんだよ。
対象環境
- Raspbierry pi 4 Model B
- Raspberry Pi OS buster 10.0 2020-08-20 ※
- bash 5.0.3(1)-release
$ uname -a Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux