やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

「他言語からのRuby入門」をやってみる

 6時間かかった。

情報源

方針

 他言語からのRuby入門を読んでirbで触ってみる。

 言語別のリンクはとりあえず放置。まずは「重要な言語機能と知っておくべきこと」だけやる。

重要な言語機能と知っておくべきこと

 17個ある。

  • イテレーション
  • すべては値(式と文に違いなし)
  • シンボル(object_id
  • すべてはオブジェクト
  • 可変な定数
  • 名前付けの規約
  • キーワード引数
  • 普遍の真理(nilfalse以外は真)
  • アクセス修飾子はスコープの最後まで適用される
  • アクセス制御(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

 配列や辞書の展開方法も同じっぽい。

 こういうことはまとめて説明してほしかった。

普遍の真理(nilfalse以外は真)

Rubyでは、nilfalseを除くすべてのものは真と評価されます。 CやPythonを始めとする多くの言語では、0あるいはその他の値、空のリストなどは 偽と評価されます。

if 0
  puts ''
else
  puts ''
end

 0は真。な、なんだってー。C言語からの慣習をぶちこわしたな。でもポストNULLnilは偽なのか。そのほうが直感的、なのかな? とにかく「nilfalse以外は真」と覚えるしかない。

アクセス修飾子はスコープの最後まで適用される

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は、親クラスCprotectedメソッドを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

 t1t2はそれぞれ異なるインスタンスである。それらをt1 == t2で比較している。このときdef ==(other)の処理が呼ばれる。その中身は9999の等価比較した結果だ。当然trueである。

 次に99を返すメソッドをpublicからprotectedへ変更する。

class Test
  protected :identifier
end

 そして==演算子オーバーロードしたメソッドからそいつを参照する。

t1 == t2  # => true

 はい、参照できちゃいました。定義されているコードをみるとother.identifierとなっている。otherというレシーバがprotectedなメソッドidentifierを呼んでいる。こうしたらprotectedでも呼び出せますよって話なんだと思う。

 え、べつに不思議はいよね? だって自分のクラスで定義したメソッド内で呼び出しているんだし。privateなメソッドだって呼び出せるよね? あれ、でもprivateselfからしか呼べないんだっけ?

 試してみよう。

class Test
  private :identifier
end
> t1 == t2
(irb):121:in `==': private method `identifier' called for #<Test:0x01c08af8> (NoMethodError)

 はい、エラーですね。たとえotherというレシーバがあっても、privateselfというレシーバからしか呼べないからエラーになったということだろう。

 ところで、==の左辺と右辺でどちらも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なら参照できちゃうよって話が本題ね。だから気をつけてね、と。

 再びidentifierprotectedにして確かめてみよう。

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

 マジか。ええと、つまり数値リテラル1Integerクラス型のインスタンスで、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の実装を理解していないと判断のしようがない。そこまで深く掘り下げるつもりはない。++って超便利なんだけど、使えない言語は結構ある。BashPythonも使えない。+=は冗長だし===と見分けがつきにくい。だから++--がほしい。でもa++++aの違いとかあるし、たまに罠になることがある。実装が大変なのかもね。

a = 1 # OK
a += 1 # OK
a++ # NG

所感

 説明不足感がある。でも、無駄がないためサクッと概要を把握できたのは嬉しい。Pythonのドキュメントはごちゃごちゃしているくせに知りたいことがわからなかったりする。サクッと概要を把握したいときはノイズばかりで、ちゃんと把握したいときは不足。そんなことになるくらいだったら、このくらいでいいんだよ。

対象環境

$ uname -a
Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux