「メソッド呼び出し(super・ブロック付き・yield)」を読む
super
はオーバーライド元のメソッド呼出に使う。
成果物
情報源
メソッド呼び出し(super・ブロック付き・yield)
- super
- ブロック付きメソッド呼び出し
- yield
- ブロックパラメータの挙動
- .() および ::() 形式のメソッド呼び出し(callメソッドの糖衣構文)
例:
foo.bar() foo.bar bar() print "hello world\n" print Class.new Class::new
文法:
[式 `.'] 識別子 [`(' [[`*'] 式] ... [`&' 式] `)'] [式 `::'] 識別子 [`(' [[`*'] 式] ... [`&' 式] `)']
メソッド呼び出し式はレシーバ(`.' の左側の式の値)のメソッドを呼び出します。レシーバが指定されない時は self のメソッドを呼び出します。
self
の説明をしてくれよ。インスタンスってことでいいよね? あとself
のメソッドを呼び出すのはクラス内で定義したメソッド内のとき限定の話だよね?
class A def m p n p self.n end private def n :n end end A.new.m
と思って試してみたら、クラス外でのself
はmain
というインスタンスを指すようだ。なにそれ。
p self #=> main def m p self #=> main end m
でもメソッド定義するときのself
はクラスを意味するっぽい。ので混乱する。そこんところちゃんと説明してほしい。
class A def self.class_method :class_method end def ins_method :ins_method end end A::class_method A.class_method #A::ins_method #=> undefined method `ins_method' for A:Class (NoMethodError) #A.ins_method #=> undefined method `ins_method' for A:Class (NoMethodError) #A.new::class_method #=> undefined method `class_method' for #<A:0x02094690> (NoMethodError) #A.new.class_method #=> undefined method `class_method' for #<A:0x015846a8> (NoMethodError) A.new::ins_method A.new.ins_method
.' と
::' とはほぼ同じ意味です。
.
と::
はほぼ同じ意味らしい。たしかに先述のコードではインスタンスメソッド、クラスメソッドどちらも.
や::
で呼び出せた。
但し、定数を表す場合は、 `::' を使わなければいけません(例: Math::PI)。逆に、
Klass::Foo
とした場合、常に定数と見なされるという制限があります。 `::' を、クラスメソッド呼び出しに使うという流儀がありますが、この点は要注意です。大文字で始まるメソッド名を使用する場合は
Klass.Foo
と `.' を使うか、
Klass::Foo()
と括弧でメソッド呼び出しであることを明示する必要があります。
定数とクラスメソッド、同名のものを用意してそれぞれの方法で呼んでみた。
class C Max = 111 def self.Max :Max end end p C::Max #=> 111 p C.Max #=> :Max p C::Max() #=> :Max p C.Max() #=> :Max
命名規則の話になる。いくつもの方法があって、どれが最善か考えることになりそう。
これまでの情報をまとめよう。
- 定義
- 定数は頭文字が大文字の変数である
- メソッドは頭文字を大文字にすることもできる
- 呼出
- 頭文字が大文字で
::
のとき定数であるとみなす - 頭文字が大文字で
::
のとき末尾に()
があればメソッドであるとみなす
- 頭文字が大文字で
強制 | 記号 | 解釈 |
---|---|---|
制限 | ::Const |
定数 |
流儀 | ::Method() |
クラスメソッド呼出 |
流儀 | .Method |
クラスメソッド呼出 |
C++など他のプログラミング言語ではクラスメソッドを呼び出すときClass::Method
のように.
でなく::
にすることがある。しかしRubyでは::
が使われたら定数であるとみなす。むしろ定数を参照するときは必ず::
でなければならない。
だとしたらメソッド呼出は必ず.
にしたほうが良さそうか? いや、それだとクラスメソッドかインスタンスメソッド、どちらなのか見分けづらくなってしまう。最適なのは以下だと思われる。
名前 | 部品 | スコープ演算子 | case |
---|---|---|---|
ins.ins_method |
インスタンスメソッド | . |
snake_case |
Cls::cls_method |
クラスメソッド | :: |
snake_case |
Cls::CONST |
定数 | :: |
SNAKE_CASE |
定数とクラスメソッドの名前重複が起こらないようにした。
- 定義
- ファイル名は
snake_case
- クラス、モジュール名は
CamelCase
- メソッド名は
snake_case
- 定数は
SNAKE_CASE
- ファイル名は
- 呼出
- インスタンスメソッドは
.
で呼ぶ - それ以外は
::
で呼ぶ(定数、クラスメソッド、モジュール関数)
- インスタンスメソッドは
もしクラスメソッドをキャメルケースにしたら覚えにくくなる。
Rubyは頭文字を大文字にすると定数になる。その制約だとキャメルケースCamelCase
にも対応できてしまう。それだとクラス名と同じになり見分けがつかない。また、メソッド名は制約がないためキャメルケースにもできてしまう。やはり見分けがつかなくなる。そこで定数はすべて大文字のスネークケースにする。これで文字種だけで定数かそれ以外であると識別できる。
::
は一貫してトップスコープ、クラススコープ、モジュールスコープを指すときに使う。インスタンスメソッド呼出は必ず.
で呼ぶ。あるいはクラス定義内におけるメソッドの中にあるself.
は省略したほうが短く書けて良い。
ちなみにセッター、ゲッターに関しては@
がないインスタンス 変数名のメソッド名になる。attr
,attr_reader
,attr_writer
の糖衣構文によってその名前のメソッドが自動生成されるため、それに倣う形だ。C#だったらそれらプロパティはキャメルケースになるが、Rubyではスネークケースである。また、プロパティという種別ではなくメソッドである。
class B attr_accessor :name def initialize @name = 'ytyaru' end end b = B.new p b.name #=> 'ytyaru' b.name = 'YTYARU' p b.name #=> 'YTYARU'
メソッド名には通常の識別子の他、識別子に ? または ! の続いたものが許されます。慣習として、述語(真偽値を返すメソッド)には ? を、同名の(! の無い)メソッドに比べてより破壊的な作用をもつメソッド(例: tr と tr!)には ! をつけるようになっています。
地味に大事。
引数の直前に * がついている場合、その引数の値が展開されて渡されます。展開はメソッド to_a を経由して行なわれます。つまり:
foo(1,*[2,3,4]) foo(1,*[]) foo(1,*[2,3,4],5) foo(1,*[2,3,4],5,*[6])
は、それぞれ
foo(1,2,3,4) foo(1) foo(1,2,3,4,5) foo(1,2,3,4,5,6)
と同じです。
配列展開ね。Pythonにもあったやつ。
ハッシュ(辞書、連想配列)でも同じことができる。なぜかこの公式ドキュメントにまったく説明がないけど。展開するときは**
を頭につける。メソッドの引数にもできる。ようするにPythonと同じ。
def m(*args, **kwargs) p args #=> [1, 2, 3] p kwargs #=> {:key=>"value", :k2=>"v2"} end m *[1,2,3], **{key:'value', k2:'v2'}
最後の引数の直前に & がついている場合、その引数で指定した手続きオブジェクト(Proc)やメソッドオブジェクト(Method)がブロックとしてメソッドに渡されます。詳細は ブロック付きメソッド呼び出し を参照してください。
コードで書くと以下。
def m yield end m { p 'ブロック付きメソッド呼び出し1' }
def m(&block) block.call() end m { p 'ブロック付きメソッド呼び出し2' }
メソッド呼び出しの際、private なメソッドは関数形式(レシーバを省略した形式)でしか呼び出すことができません。また protected なメソッドはそのメソッドを持つオブジェクトのメソッド定義式内でなければ呼び出せません。クラス/メソッドの定義/呼び出し制限 を参照して下さい。
private
メソッド呼出に関しては困ることはないと思う。もし親クラスのprotected
やpublic
なメソッドと名前重複したら、親クラスのそれらにsuper
を付与して呼び出せばよいのだと思う。
あとデザインパターン的に、継承よりも集約するほうが疎結合になって良いとされる。なのでprotected
に頼るような実装は避けたほうがよいかもしれない。
ハッシュ式の解説(リテラル/ハッシュ式)にも書かれている通り、メソッド呼出の末尾にHashを渡す場合は {, } を省略することができます。これを用いることでキーワード引数を渡すことができます。この Hash はクラス/メソッドの定義/メソッド定義で解説されているキーワード引数に分解されて引き渡されます。
ハッシュをメソッド引数として渡すとキーワード引数になる。それはいいのだが、{,}
の省略って嬉しいか? =>
を使うと逆に長くなってしまうこともあるんだが。ふつうに名前:値
のキーワード引数形式のほうがいいと思う。
def m(a:, b:, c:) p "#{a} #{b} #{c}" end m **{a:'A', b:'B', c:'C'} #m a=>'A', b=>'B', c=>'C' #=> undefined local variable or method `a' for main:Object (NameError) #m :a=>'A', :b=>'B', :c=>'C' # 正常だが冗長。 => を使う意味がない #m 'a'=>'A', 'b'=>'B', 'c'=>'C' #=> missing keywords: :a, :b, :c (ArgumentError) #m a='A', b='B', c='C' #=> wrong number of arguments (given 3, expected 0; required keywords: a, b, c) (ArgumentError) m a:'A', b:'B', c:'C'
メソッド呼び出しで
.' の代わりに
&.' を使うことができます。この形式でメソッドを呼びだそうとすると、レシーバが nil の場合は以下のように働きます。
foo = 13 foo&.to_s # => "13" foo = nil foo&.to_s # nil, not ""
`&.' は要素代入(アトリビュート)に対しても使えます。
foo&.bar = "abc" # for `bar=' method
つまりNULL参照エラーを回避できるってことか。これは便利。
レシーバがnil
になりうるときだけ&.
参照すれば実行せずに通過してくれる。
super
例:
super super(1,2,3)
文法:
super super(式, ... )
super は現在のメソッドがオーバーライドしているメソッドを呼び出します。括弧と引数が省略された場合には現在のメソッドの引数がそのまま引き渡されます。引数を渡さずにオーバーライドしたメソッドを呼び出すには super() と括弧を明示します。
例:
class Foo def foo(arg=nil) p arg end end class Bar < Foo def foo(arg) super(5) # 5 を引数にして呼び出す super(arg) # 5 を引数にして呼び出す super # 5 を引数にして呼び出す super(arg) の略記法 arg = 1 super # 1 を引数にして呼び出す super(arg) の略記法 super() # 引数なしで呼び出す end end Bar.new.foo 5
え、super
って親インスタンスでなく親メソッドなの?! 他言語と違う。
そういえばRubyは継承だけでなくMixinできるからなぁ。super
が何を指しているのか一意に特定できるのはオーバーライドしたメソッドだけってことか。親クラスはMixinされちゃったらどれがsuper
か一意に特定できない。
Rubyはたしかancestors
で継承やMixinしたクラスのリストを取得できる。その順に指定したメソッドがあればそれを返す。なのでsuper
の指定は他言語とちがう。
class D def m(msg) p "D: #{msg}" end end class E < D def m(msg) print 'E: ' super(msg) end end E.new.m '引数' #=> E: D: 引数
継承やMixinした他のメソッドはself
やその省略で呼べるはず。ただ、同名のオーバーライド元だけはsuper
で呼び出す。それ以外に指名する方法がないから。
ブロック付きメソッド呼び出し
例:
[1,2,3].each do |i| print i*2, "\n" end [1,2,3].each {|i| print i*2, "\n" }
文法:
method(arg1, arg2, ...) do [`|' 式 ... `|'] 式 ... end method(arg1, arg2, ...) `{' [`|' 式 ... `|'] 式 ... `}' method(arg1, arg2, ..., `&' proc_object)
ブロック付きメソッドとは制御構造の抽象化のために用いられるメソッドです。最初はループの抽象化のために用いられていたため、特にイテレータと呼ばれることもあります。 do ... end または { ... } で囲まれたコードの断片 (ブロックと呼ばれる)を後ろに付けてメソッドを呼び出すと、そのメソッドの内部からブロックを評価できます。ブロック付きメソッドを自分で定義するには yield 式を使います。
やっと説明きたよ。ブロック付きメソッド
=イテレータ
ってことね。ほかの言語からしてみると全然違うんだけど。クロージャのことだと思うけど。
{ ... } の方が do ... end ブロックよりも強く結合します。次に例を挙げますが、このような違いが影響するコードは読み辛いので避けましょう:
foobar a, b do .. end # foobarの引数はa, bの値とブロック foobar a, b { .. } # ブロックはメソッドbの引数、aの値とbの返り値とがfoobarの引数
ん? 違いが影響するコード?
そもそもdo..end
と{}
の違いって、1行で書けるかどうかの違いがあるよね? それを先に説明してほしいんだが。と思ったら{}
でも複数行で書けた。あれ?
def m yield end m {p "call"} m { p "call" } m do p "call"; end m do p "call" end
まあいいや。読み進めよう。
ブロックの中で初めて代入された(宣言された)ローカル変数はそのブロックの中でだけ有効です。例えば:
foobar { i = 20 # ローカル変数 `i' が宣言された ... } print defined? i # `i' はここでは未定義なので false foobar a, b do i = 11 # まったく別の変数 i の宣言 ... end
以下は逆にブロック外でも有効な例です。
i = 10 [1,2,3].each do |m| p i * m # いきなり i を使える end
スコープが違うってことね。
ブロック | 外側の変数を参照 |
---|---|
{} |
できない |
do..end |
できる |
その話は知ってた。
ブロックの部分だけを先に定義して変数に保存しておき、後からブロック付きメソッドに渡すことも出来ます。それを実現するのが手続きオブジェクト(Proc)です。それをブロックとして渡すにはブロック付きメソッドの最後の引数として `&' で修飾した手続きオブジェクトを渡します。Proc の代わりにメソッドオブジェクト(Method)を渡すことも出来ます。この場合、そのメソッドを呼ぶ手続きオブジェクトが生成され渡されます。
# 1引数の手続き(その働きは引数をpで印字すること)を生成し、変数pobjに格納 pobj = proc {|v| p v } [1,2,3].each(&pobj) # 手続きオブジェクトをブロックの代わりに渡している => 1 2 3
クロージャを変数にするならproc
を使え、ということか。
to_proc メソッドを持つオブジェクトならば、`&' 修飾した引数として渡すことができます。デフォルトで Proc、Method オブジェクトは共に to_proc メソッドを持ちます。to_proc はメソッド呼び出し時に実行され、Proc オブジェクトを返すことが期待されます。
class Foo def to_proc Proc.new {|v| p v} end end [1,2,3].each(&Foo.new) => 1 2 3
なんであれproc
を返すならメソッド経由でもOKと。
ブロック付きメソッドの戻り値は、通常のメソッドと同様ですが、ブロックの中から 制御構造/break により中断された場合は nil を返します。
はい。
break に引数を指定した場合はその値がブロック付きメソッドの戻り値になります。
はい。
yield
自分で定義したブロック付きメソッドでブロックを呼び出すときに使います。 yield に渡された値はブロック記法において | と | の間にはさまれた変数(ブロックパラメータ)に代入されます。
yield
はブロック呼出式である。あきらかに他の言語のイテレータ、ジェネレータと違う。
例:
yield data
文法:
yield `(' [式 [`,' 式 ... ]] `)' yield [式 [`,' 式 ... ]]
引数をブロックパラメータとして渡してブロックを評価します。yield はイテレータを定義するために クラス/メソッドの定義/メソッド定義 内で使用します。
# ブロック付きメソッドの定義、 # その働きは与えられたブロック(手続き)に引数1, 2を渡して実行すること def foo yield(1,2) end # fooに「2引数手続き、その働きは引数を配列に括ってpで印字する」というものを渡して実行させる foo {|a,b| p [a, b] } # => [1, 2] (要するに p [1, 2] を実行した) # 今度は「2引数手続き、その働きは足し算をしてpで印字する」というものを渡して実行させる foo {|a, b| p a + b } # => 3 (要するに p 1 + 2 を実行した) # 今度のブロック付きメソッドの働きは、 # 与えられたブロックに引数10を渡して起動し、続けざまに引数20を渡して起動し、 # さらに引数30を渡して起動すること def bar yield 10 yield 20 yield 30 end # barに「1引数手続き、その働きは引数に3を足してpで印字する」というものを渡して実行させる bar {|v| p v + 3 } # => 13 # 23 # 33 (同じブロックが3つのyieldで3回起動された。 # 具体的には p 10 + 3; p 20 + 3; p 30 + 3 を実行した) # Array#eachの(粗製乱造の)類似品 def iich(arr) # 引数に配列を取る idx = 0 while idx < arr.size yield(arr[idx]) # 引数の各要素毎に、その要素を引数にしてブロックを起動 idx += 1 end end sum = 0 iich([1, 4, 9, 16, 25]) {|elem| sum += elem } p sum # => 55
ブロックパラメータの代入は演算子式/多重代入と同じルールで行われます。また yield を実行したメソッドにブロックが渡されていない (ブロック付きメソッド呼び出しではない)時は例外 LocalJumpError が発生します。
記法は|p1,p2,...|
と特殊。
yield はブロック内で最後に評価した式の値を返します。また、 制御構造/next によりブロックの実行が中断された場合は nil を返します。
はい。
next に引数を指定した場合はその値が yield の戻り値になります。
はい。
ブロックパラメータの挙動
メソッド呼び出しと挙動が異なります。 lambda でないブロックを呼び出したとき
- 引数の数が違ってもエラーになりません。
- 配列をひとつ渡したときにそれが引数の並びとして展開されることがあります。
lambda
の説明って今まで一度も出てこなかったよね? ここで急に出てきたよ。ちゃんと説明してよ。
def foo yield 1,2,3 end foo{|v| p v} #=> 1 def bar yield [1,2,3] end bar{|a, b, c| p a} #=> 1 def hoge yield [1,2,3],4,5 end hoge{|a, b, c| p a} #=> [1,2,3]
http://www.a-k-r.org/d/2007-08.html#a2007_08_16_1
.() および ::() 形式のメソッド呼び出し(callメソッドの糖衣構文) 下記はcallメソッドの糖衣構文です。 Proc#callにも言及がありますが、Proc以外のオブジェクトに対しても(callメソッドさえ定義されていれば)使えます。
例:
foo.(100) # foo.call(100)と全く同じ意味 foo::(100) # foo.call(100)と全く同じ意味 foo.(100) # foo.call(100)と全く同じ意味 foo::(100) # foo.call(100)と全く同じ意味
文法:
式 `.' `(' [[`*'] 式] ... [`&' 式] `)' 式 `::' `(' [[`*'] 式] ... [`&' 式] `)'
これはスタイルガイドで非推奨だった記法。call()
を使ったほうが良さそう。
所感
super
が以外だった。
対象環境
- Raspbierry pi 4 Model B
- Raspberry Pi OS buster 10.0 2020-08-20 ※
- bash 5.0.3(1)-release
- Ruby 3.0.2
$ uname -a Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux