内部クラスも定義できる。新たに知ったのはそのくらい。
成果物
情報源
クラス/メソッドの定義
- クラス/メソッドの定義:
- クラス定義
- 特異クラス定義
- モジュール定義
- メソッド定義
- 演算子式の定義
- メソッド定義のネスト
- メソッドの評価
- 特異メソッド定義
- クラスメソッドの定義
- 呼び出し制限
- 定義に関する操作:
- alias
- undef
- defined?
クラス定義
例:
class Foo < Super def test : end : end
文法:
class 識別子 [`<' superclass ] 式.. end
文法:
class 識別子 [`<' superclass ] 式.. [rescue [error_type,..] [=> evar] [then] 式..].. [else 式..] [ensure 式..] end
クラスを定義します。クラス名はアルファベットの大文字で始まる識別子です。
頭文字は大文字でないとエラーになる。
class a; end #=> class/module name must be CONSTANT
rescue/ensure 節を指定し、例外処理ができます。例外処理については制御構造/begin参照。
クラス定義で例外処理したがる動機があるのか? わからん。
クラス定義は、識別子で指定した定数へのクラスの代入になります (Ruby では、クラスもオブジェクトの一つで Classクラスのインスタンスです)。
クラスは定数の一種。なので定数と同じく頭文字が大文字。
クラスが既に定義されているとき、さらに同じクラス名でクラス定義を書くとクラスの定義の追加になります。ただし、元のクラスと異なるスーパークラスを指定すると TypeError が発生します。
class Foo < Array def foo end end # 定義を追加(スーパークラス Array を明示的に指定しても同じ) class Foo def bar end end # 間違ったスーパークラスを指定するとエラー class Foo < String end # => superclass mismatch for class Foo (TypeError)
クラスは再定義できてしまう。このせいでバグがどこにあるのか探すのに全コードファイルを見なければならない。カプセル化の意義が失われてしまうほどの残念ぶり。
クラス定義式の中は self がそのクラスであることと、 呼び出し制限のデフォルトが異なること以外にトップレベルとの違いはありません。クラス定義式中には任意の式を書くことができクラス定義の際に実行されます。
でもインスタンスメソッド定義内におけるself
はクラスではなくインスタンスを指す。紛らわしい。
クラス定義はネスト(入れ子)にして定義できます。以下の例で入れ子の外側のクラス Foo と内側のクラス Bar の間には、定数 Bar が Foo の中の定数 Foo::Bar であること以外、継承関係などの機能的な関連はまったくありません。
class Foo class Bar end end
クラス Foo が既に定義されていれば、以下の書き方もできます。
class Foo end class Foo::Bar end
いわゆる内部クラスというやつか。private
にできるのかな? 試してみたがエラーにならなかったのでprivate
にはできないっぽい。
class C private class D; end end C.new C::D.new
念のためググったらprivate_constant
というのでできるっぽい。
class E class F; end private_constant :F end E.new E::F.new #=> private constant E::F referenced (NameError)
相変わらず情報がまとまっていない。またも公式ドキュメント外で新たな情報を発見した。こんなことばっかり。本当にこのドキュメントで構文を学べるのか? 甚だ疑問。
クラスのネストは、意味的に関連するクラスを外側のクラス/モジュールでひとまとまりにしたり、包含関係を表すために使用されます。
# 関連するクラスを Net というカテゴリにまとめる # このような場合は外側は普通モジュールが利用される # (Net のインスタンスがない。Net を include できるなどのため) module Net class HTTP end class FTP end end obj = Net::HTTP.new # あるいは include Net obj = HTTP.new # 以下のような使い方は組み込みのクラスにも見られる # 利用者は File::Constants を include することで、 # File::RDONLY などと書かずに直接 RDONLY と書くことができる。 class File module Constants RDONLY = 0 WRONLY = 1 end include Constants end File.open("foo", File::RDONLY) # あるいは include File::Constants File.open("foo", RDONLY) # 上記はあくまでも例である。実際の File.open ではより簡便な # File.open("foo", "r") という形式が使われる
ようするに内部クラスや内部モジュールは名前空間として使えるってことね。
クラス定義式は、最後に評価した式の結果を返します。最後に評価した式が値を返さない場合は nil を返します。
クラス定義ですら式であり値を返す。はたしてそれに何の意味があるのかは知らないが。
特異クラス定義
例:
obj = Object.new # obj = nil でも可 class << obj def test : end : end
文法:
class `<<' expr 式.. end
文法:
class `<<' expr 式.. [rescue [error_type,..] [=> evar] [then] 式..].. [else 式..] [ensure 式..] end
クラス定義と同じ構文で特定のオブジェクトにメソッドやインスタンス変数を定義/追加します。この構文の内部で定義したメソッドや定数は指定したオブジェクトに対してだけ有効になります。 Object#clone で生成したオブジェクトには引き継がれますが, Object#dup で生成したオブジェクトには引き継がれません.
ややこしいな。clone
だのdup
だの知らんよ。
rescue/ensure 節を指定し、例外処理ができます。例外処理については制御構造/begin参照。
定義の時点で例外処理したがる理由がわからない。
特異クラス定義式は、最後に評価した式の結果を返します。最後に評価した式が値を返さない場合は nil を返します。
やはりこれも式であり値を返すようだ。その意義はわからない。
できる、というだけでなくベストプラクティスを絡めて話してほしい。
モジュール定義
例:
module Foo def test : end : end
文法:
module 識別子 式.. end
文法:
module 識別子 式.. [rescue [error_type,..] [=> evar] [then] 式..].. [else 式..] [ensure 式..] end
モジュールを定義します。モジュール名はアルファベットの大文字で始まる識別子です。
クラスと同じ。
rescue/ensure 節を指定し、例外処理ができます。例外処理については制御構造/begin参照。
クラスと同じ。
モジュール定義は、識別子で指定した定数へのモジュールの代入になります。 Ruby では、モジュールもオブジェクトの一つで Module クラスのインスタンスです。モジュールが既に定義されいるとき、さらに同じモジュール名でモジュール定義を書くとモジュールの定義の追加になります。
クラスと同じ。
モジュール定義式は、最後に評価した式の結果を返します。最後に評価した式が値を返さない場合は nil を返します。
クラスと同じ。
これではクラスとの違いがわからない。私が覚えている違いをメモっておく。
module
はclass
と違ってnew
できないmodule
はclass
にinclude
,prepend
,extend
される側module
はモジュール関数の定義ができる(スタティック関数のようなもの)
メソッド定義
例:
def fact(n) if n == 1 then 1 else n * fact(n-1) end end
文法:
def メソッド名 ['(' [arg0 ['=' default0]] ... [',' '*' rest_args [, post ...]] [',' key1: [val1]] ... [',' '**'kwrest] [',' '&' block_arg]`)'] 式.. (body) [rescue [error_type,..] [=> evar] [then] 式..].. [else 式..] [ensure 式..] end
この定義のある場所にメソッドを定義します。すなわち、クラス/モジュール定義中ならばそのクラス/モジュールのメソッドを定義します。トップレベルならばどこからでも呼べるメソッドを定義します。このようなメソッドは結果として他の言語における「関数」のように使えます。
例:
def hello # 引数のないメソッド。 puts "Hello, world!" end def foo(a, b) # 引数のあるメソッド。括弧を省いてdef foo a, bとも a + 3 * b end
はい、知ってます。
メソッド名としては通常の識別子の他に、再定義可能な演算子(例: ==, +, - など 演算子式 を参照)も指定できます(演算子式の定義参照)。
例:
class Vector2D attr_accessor :x, :y # インスタンス変数@x, @yに対応するゲッタとセッタを定義 def initialize(x, y) # コンストラクタ @x = x; @y = y # @がつくのがインスタンス変数(メンバ変数) end def ==(other_vec) # いわゆる演算子オーバーライド other_vec.x == @x && other_vec.y == @y end def +(other_vec) Vector2D.new(other_vec.x + @x, other_vec.y + @y) end ... end vec0 = Vector2D.new(10, 20); vec1 = Vector2D.new(20, 30) p vec0 + vec1 == Vector2D.new(30, 50) #=> true
知ってます。他の言語でいうところの演算子オーバーライド。
仮引数にデフォルト式が与えられた場合、メソッド呼び出しで実引数を省略したときのデフォルト値になります。ただし実引数との対応を取るため、i番目の引数にデフォルト値を指定したならば、 i+1番目以降でも全てデフォルト値を指定するか、可変長引数を利用しなければなりません(詳細は後述)。デフォルト式の評価は呼び出し時にメソッド定義内のコンテキストで行われます。
例:
def foo(x, y = 1) # 2番目の引数yにデフォルト値を指定 10 * x + y end p foo(1, 5) #=> 15 p foo(3) #=> 31 p foo #=> ArgumentError (wrong number of arguments) $gvar = 3 def bar(x, y = $gvar) # 確かに定義時には$gvar == 3だが 10 * x + y end $gvar = 7 # 呼び出し時の$gvarの値が使われる p bar(5) #=> 57 (!= 53)
ふつうデフォルト値には定数を使うと思うのだが。たぶん説明のためだろう。
ほかのコンパイル型プログラミング言語では変数をデフォルト値にはできない。そのへんの違いを明記したのだろう。
仮引数の直前に * がある場合には残りの実引数 (後述の post 引数を除く) はみな配列とし てこの引数に格納されます。可変長引数、rest 引数などと呼ばれる機能です。このような引数は 1 つしか作れません。
例:
def foo(x, *xs) puts "#{x} : #{xs.inspect}" # Object#inspect は p のような詳細な内部表示 end foo(1) #=> 1 : [] foo(1, 2) #=> 1 : [2] foo(1, 2, 3) #=> 1 : [2, 3] def bar(x, *) # 残りの引数を単に無視したいとき puts "#{x}" end bar(1) #=> 1 bar(1, 2) #=> 1 bar(1, 2, 3) #=> 1
C言語にもあった可変長引数ね。
Ruby 1.9 以降では可変長引数よりも後にまだ通常の引数を置くことができます。
それは混乱しそう。たしかに以下コードでエラーにならなかった。
def m(*args, p1, p2); end
どうやって渡せばいいんだ?
def m(*args, p1, p2) p args p p1 p p2 end m 1,2,3 #=> [1] 2 3 m 1,2,3,4,5 #=> [1,2,3] 4 5 m 1 #=> wrong number of arguments (given 1, expected 2+) (ArgumentError)
ああ、後ろの数から逆算しているのかな?
じゃあ前後に位置引数があったときは?
def m(p1, p2, *args, p3, p4) p p1 p p2 p args p p3 p p4 end m 1,2,3,4,5 #=> 1 2 [3] 4 5 m 1,2,3,4,5,6 #=> 1 2 [3,4] 5 6 m 1,2,3,4 #=> 1 2 [] 3 4
なるほど。位置引数の数と位置の分だけ引数があれば、あとは可変長引数ができるかぎり調整する感じか。
最後の仮引数の直前に & があるとこのメソッドに与えられているブロックが手続きオブジェクト(Proc)としてこの引数に格納されます。これは、イテレータを定義する方法の一つです。イテレータを定義する代表的な方法は yield を呼び出すことです。他に Proc.new/Kernel.#proc を使う方法などもあります。ブロックが与えられなかった場合のブロック引数の値はnilです。
例:
def foo(cnt, &block_arg) cnt.times { block_arg.call } # ブロックに収まったProcオブジェクトはcallで実行 end foo(3) { print "Ruby! " } #=> Ruby! Ruby! Ruby!
はい、知ってます。他の言語からみると奇妙な書き方。
メソッド定義において、仮引数はその種類毎に以下の順序でしか指定することはできません。いずれも省略することは可能です。
- デフォルト式のない引数(複数指定可)
- デフォルト式のある引数(複数指定可)
*
を伴う引数(1つだけ指定可)- デフォルト式のない引数(複数指定可)
- キーワード引数(複数指定可)
**
を伴う引数(1つだけ指定可)&
を伴う引数(1つだけ指定可)
これは明記してくれて嬉しい。超大事。
デフォルト式のある引数と、キーワード引数って紛らわしい。
名前 | コード |
---|---|
デフォルト式のある引数 | p1 = 'v' |
キーワード引数 | p1: 'v' , p1: |
例:
# すべて持つ(極端な例なのでおすすめしない) def f(a, b, c, m = 1, n = 1, *rest, x, y, z, k: 1, **kwrest, &blk) puts "a: %p" % a puts "b: %p" % b puts "c: %p" % c puts "m: %p" % m puts "n: %p" % n puts "rest: %p" % rest puts "x: %p" % x puts "y: %p" % y puts "z: %p" % z puts "k: %p" % k puts "kwrest: %p" % kwrest puts "blk: %p" % blk end f("a", "b", "c", 2, 3, "foo", "bar", "baz", "x", "y", "z", k: 42, u: "unknown") { } #=> a: "a" b: "b" c: "c" m: 2 n: 3 rest: "foo" x: "x" y: "y" z: "z" k: 42 kwrest: {:u=>"unknown"} blk: #<Proc:0x007f7e7d8dd6c0@-:16>
例: イテレータの定義
# yield を使う def foo # block_given? は、メソッドがブロックを渡されて # 呼ばれたかどうかを判定する組み込み関数 if block_given? yield(1,2) end end # Proc.new を使う def bar if block_given? Proc.new.call(1,2) # proc.call(1,2) でも同じ(proc は組み込み関数) end end # 応用: 引数として Proc オブジェクトとブロックの # 両方を受け付けるイテレータを定義する例 def foo(block = Proc.new) block.call(1,2) end foo(proc {|a,b| p [a,b]}) foo {|a,b| p [a,b]} # ブロック引数を使う def baz(&block) if block block.call(1,2) end end
というかif block
は&.
を使えば省略できるはずでは? &.
で参照すると左辺レシーバがnil
のときは実行しないんだったよね?
メソッド呼び出しで
.' の代わりに
&.' を使うことができます。この形式でメソッドを呼びだそうとすると、レシーバが nil の場合は以下のように働きます。
というわけで書いてみる。ついでに呼出の()
も省略。
def m(&block) block&.call 1, 2 end m {|a,b| p '#{a} #{b}'} m #m proc {|a,b| p '#{a} #{b}'} #=> wrong number of arguments (given 1, expected 0) (ArgumentError)
でもproc
は受け付けなかった。デフォルト値ありで省略できるようにしないとダメらしい。さらにブロック引数&
にしてはダメっぽい。めんどくせぇ……。
def m(block = proc {}) block&.call 1, 2 end m m {|a,b| p '#{a} #{b}'} m proc {|a,b| p '#{a} #{b}'}
できた。ブロック{}
、do..end
, proc
, lambda
はクロージャとして統一してほしい。定義の記法も違えば仮引数の記法まで違う。ほぼ同じことを指すのに記法が違うのは紛らわしいだけ。これは書いてて楽しくないコードだ。
あとさ、前から思っていたけどブロック引数ってひとつしか作れないよね? ヒアドキュメントみたく複数書けたらよかったのに。
p <<FIRST, <<SECOND 最初 FIRST 二番目 SECOND
あ、でもメソッド定義には書けないみたい。
def m(<<~FIRST) #=> syntax error, unexpected string literal, expecting ')' 最初 FIRST end
仮に複数のブロックを追加できるメソッド定義記法があっても意味はなさそう。
def m(p1, p2, <<INI, <<FIN) p '開始処理' INI p '終了処理' FIN INI.call p '中心処理' FIN.call end
やるなら呼出だけど、あまり美しくない。
def m(p1, p2, INI=nil, FIN=nil) INI&.call p '中心処理' FIN&.call end m 1, 2 { p '開始処理' } { p '終了処理' }
現状でもproc
変数に代入すれば複数のproc
呼出を書ける。でもブロックはダメ。
def m(p1, p2, ini=nil, fin=nil) ini&.call p "#{p1} #{p1}" fin&.call end m 1, 2 m 1, 2, proc{p '開始'}, proc{p '終了'} m(1, 2) {p '開始'} # ブロックは実行されない #m 1, 2 {p '開始'} #=> syntax error, unexpected '{', expecting end-of-input #m 1, 2 {p '開始'} {p '終了'} #=> syntax error, unexpected '{', expecting end-of-input #m 1, 2, {p '開始'}, {p '終了'} #=> syntax error, unexpected string literal, expecting `do' or '{' or '('
ブロック引数はひとつだけしか定義できない。
def m(p1, p2, &ini=nil, &fin=nil) #=> syntax error, unexpected '=', expecting ')' ini&.call p "#{p1} #{p1}" fin&.call end
処理を引数にしたいときは以下のようになる。
- 処理引数がひとつである
- ブロック
{}
である- ブロック引数省略
- ブロック引数明記
proc{}
である- デフォルト式なし(必須)
- デフォルト式あり(任意)
- 引数定義:
f = nil
- 呼出:
f&.call
- 引数定義:
- ブロックと
proc
の両方受け付ける- 引数定義:
f = nil
- 呼出:
f&.call
- 引数定義:
- ブロック
- 処理引数がふたつ以上ある
proc{}
である- デフォルト式なし(必須)
- デフォルト式あり(任意)
- 引数定義:
f = nil
- 呼出:
f&.call
- 引数定義:
ムダに記法が多い。たぶんRubyの構文を改善しつづけてきて、徐々に面倒くさくなってきたのだろう。単にクロージャの扱いとして統一してほしいのが本音。ほかのプログラミング言語とくらべて面倒くさすぎる。
またメソッド実行時の例外を捕捉するために begin 式と同様のrescue, else, ensure 節を指定できます。例外処理については制御構造/begin参照。
メソッド「定義時」ではなく「実行時」と書いてある。これなら使うことはよくあるだろう。
メソッド定義式は、メソッド名を Symbol にしたオブジェクトを返します。
それを使いたがるときはあるのだろうか?
m = def m; end
@see https://magazine.rubyist.net/articles/0041/0041-200Special-kwarg.html