Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyのシンタックスシュガー徹底解説:name=や[]メソッドの仕組みと自作テクニック

Ruby Magicシリーズへようこそ!今回のテーマはシンタックスシュガーです。Rubyがどのように構文を読みやすく、表現力豊かにしているのかを掘り下げていきます。この記事を読み終える頃には、Rubyのいくつかのトリックが内部でどう動いているのか、そして自分でも「糖衣構文」を活かしたメソッドを書く方法がわかるはずです。

Rubyアプリケーションを書いていると、クラスの属性や配列・ハッシュを、他の言語では標準的とは言えない方法で操作することがよくあります。属性を代入するメソッドや、配列・ハッシュから値を取り出すメソッドは、一体どうやって定義すればよいのでしょうか?

person1 = Person.new
person1.name = "John"
 
array = [:foo, :bar]
array[1]  # => :bar
 
hash = { :key => :foo }
hash[:key] # => :foo
hash[:key] = :value

実はRubyには、これらのメソッド呼び出しが自然に機能するようにするためのシンタックスシュガーが用意されています。今回はその仕組みを詳しく見ていきましょう。

シンタックスシュガーとは?

シンタックスシュガーとは、より読みやすく簡潔なコードを書けるようにRubyが提供してくれる小さな✨魔法✨のようなものです。具体的には、特定の記号やスペースを省略したり、何らかのヘルパーを使って式を書き換えたりすることを指します。

メソッド名に使える特殊文字

まずはメソッド名から始めましょう。Rubyでは、他の言語ではあまりサポートされていない文字や特殊記号をメソッド名に使用できます。Railsアプリを書いたことがあるなら、save!メソッドを目にしたことがあるはずです。これはRails固有の機能ではなく、Rubyのメソッド名に!という文字が使えることを示す好例です。

=[]?%&|<>*-+/といった記号も同様に利用できます。

これらの文字が使えるおかげで、メソッド名だけでその目的を明確に示せます。

  • 属性への代入:person.name = "foo"
  • 真偽を問う質問:person.alive?
  • 危険な操作の実行:car.destroy!
  • オブジェクトに別の振る舞いを持たせる:car[:wheels]

属性メソッドの定義

attr_accessorでクラスに属性を定義すると、Rubyはインスタンス変数に対するリーダーメソッドとライターメソッドを自動的に生成します。

class Person
  attr_accessor :name
end
 
person = Person.new
person.name = "John"
person.name # => "John"

内部では、Rubyは次の2つのメソッドを作成しています。

  • Person#nameattr_readerによって生成される、クラスの属性(インスタンス変数)を読み取るためのメソッド
  • Person#name=attr_writerによって生成される、クラスの属性(インスタンス変数)に書き込むためのメソッド

では、この挙動をカスタマイズしたい場合はどうすればよいでしょうか。attr_accessorヘルパーを使わずに、自分でメソッドを定義してみます。

class AwesomePerson
  def name
    "Person name: #{@name}"
  end
 
  def name=(value)
    @name = "Awesome #{value}"
  end
end
 
person = AwesomePerson.new
person.name = "Jane"
person.name # => "Person name: Awesome Jane"

name=のメソッド定義は、person.name = "Jane"というメソッド呼び出しの書き方とほぼ同じ形をしています。呼び出し側では=の前後にスペースを入れず、括弧も使いません。

括弧とスペースは省略できる

Rubyでは多くの場合、括弧を省略できます。メソッドに引数を渡すとき、引数を括弧()で囲む必要はありませんが、読みやすさのために囲んでも問題ありません。

if文は良い例です。多くの言語ではif文が評価する条件式を括弧で囲みますが、Rubyでは省略できます。

puts "Hello!" if (true) # 括弧あり
puts "Hello!" if true   # 括弧なし

メソッド定義やその他の式でも同じことが言えます。

def greeting name # 括弧を省略
  "Hello #{name}!"
end
 
greeting("Robin") # 括弧あり
greeting "Robin"  # 括弧なし
greeting"Robin"   # 括弧もスペースもなし

最後の行はかなり読みにくいですが、実際に動作します。メソッド呼び出し時にも括弧とスペースは省略可能なのです。

ただし、すべての括弧とスペースを省略するのは避けましょう。Rubyがコードの意図を正しく理解するために必要な場合があるからです。迷ったら引数を括弧で囲めば、自分にとってもRubyにとっても、どの引数がどのメソッド呼び出しに属するのか明確になります。

以下の呼び出し方はすべて有効ですが、一般的には括弧を省略してスペースを入れ、コードを読みやすくするのが慣習です。

# 先ほどのメソッド定義:
# def name=(value)
#   @name = "Awesome #{value}"
# end
 
person.name = "Jane"
person.name="Jane"
person.name=("Jane") # メソッド定義とそっくりですね!

これでname属性用のカスタムリーダー・ライターメソッドが定義できました。必要に応じて挙動を自由にカスタマイズでき、コールバックを使わなくても、属性の代入時に直接値の変換処理を行えます。

[]メソッドの定義

次に見ていくのは、Rubyの角括弧メソッド[ ]です。配列のインデックスやハッシュのキーから値を取得・代入するときによく使われます。

hash = { :foo => :bar, :abc => :def }
hash[:foo]        # => :bar
hash[:foo] = :baz # => :baz
 
array = [:foo, :bar]
array[1] # => :bar

これらのメソッドがどのように定義されているのか見てみましょう。hash[:foo]と書くとき、私たちはRubyのシンタックスシュガーを利用しています。同じ意味を別の書き方で表すと、次のようになります。

hash = { :foo => :bar }
hash.[](:foo)
hash.[]=(:foo, :baz)
# さらに言えば:
hash.send(:[], :foo)
hash.send(:[]=, :foo, :baz)

普段の書き方(hash[:foo]hash[:foo] = :baz)と比べると、違いが見えてきます。最初の例(hash.[](:foo))では、Rubyが最初の引数を角括弧の中に移動させてhash[:foo]という形にしています。hash.[]=(:foo, :baz)の場合は、第2引数が値として渡され、hash[:foo] = :bazとなるのです。

この仕組みがわかれば、Rubyが理解できる形で自分自身の[ ]メソッドと[ ]=メソッドを定義できます。

class MyHash
  def initialize
    @internal_hash = {}
  end
 
  def [](key)
    @internal_hash[key]
  end
 
  def []=(key, value)
    @internal_hash[key] = value
  end
end

これらが普通のRubyメソッドだとわかったので、他のメソッドと同じロジックを適用できます。例えば、[ ]メソッドで複数のキーを受け取れるようにするといった、少し変わったことも可能です。

class MyHash
  def initialize
    @internal_hash = { :foo => :bar, :abc => :def }
  end
 
  def [](*keys)
    @internal_hash.values_at(*keys)
  end
end
 
hash = MyHash.new
hash[:foo, :abc] # => [:bar, :def]

自分だけのシンタックスシュガーを作ろう

Rubyのシンタックスシュガーについて理解できたところで、この知識を活かしてカスタムライターやHash風クラスなど、独自のメソッドを作ってみましょう。

実は、中身はArrayやHashではないのに、あたかもそうであるかのように振る舞わせるために角括弧メソッドなどを定義しているgemは驚くほど多く存在します。一例として、Railsアプリでフラッシュメッセージを設定する
flash[:alert] = "An error occurred"があります。AppSignal gemでも、設定値を取得するショートカットとしてConfigクラスでこの手法を採用しています。

Rubyにおけるメソッド定義と呼び出しのシンタックスシュガーについての解説は以上です。この記事が気に入ったか、疑問点があるか、次にどんな記事を読みたいか、ぜひ@AppSignalまでお知らせください。

  1. Rubyメソッドをスパイする方法!TracePointで実行をトレースしよう

    Rubyには、TracePointクラスを使ってアクセスできる組み込みのトレーシングシステムが備わっています。このシステムを利用すると、メソッド呼び出し、新しいスレッドの開始、例外の発生などをトレースできます。 なぜこれを使いたくなるのでしょうか? たとえば、特定のメソッドの実行過程を追跡したい場合に非常に便利です。その処理の中で他にどのメソッドが呼ばれているのか、どんな戻り値が返されているのかを、目で確認できるようになります。 それでは、いくつか具体例を見ていきましょう! メソッド呼び出しをトレースする 多くの場合、TracePointでトレースしたいのはアプリケーション側のコードであり、

  2. Rubyの配列(Array)クラスの使い方徹底解説!基本操作と便利なメソッド一覧

    配列(Array)とは? 配列はRubyに組み込まれたクラスで、0個以上の要素をリスト形式で保持することができます。さらに、要素の追加・アクセス・繰り返し処理を簡単に行えるメソッドも多数用意されています。 もし配列が存在しなければ、値ごとに個別の変数を用意しなければならず、管理が非常に大変になります。 例: a = 1 b = 2 c = 3 しかし、配列を使えば次のように書けます: numbers = [1, 2, 3] さらに嬉しいことに、配列の中には何でも入れられます! 例えば: 数値 文字列 さらに別の配列!(これは多次元配列になります) それでは、配列を最大限に活用できる