Rubyスタイルガイドを読む: 文法(7)lambda、標準入出力など

こんにちは、hachi8833です。

「Rubyスタイルガイドを読む」シリーズ、そろそろ文法編も終わりかと思いきやもう少し続きます。今回はlambda周りです。

文法(7) lambda、標準入出力など

2-47【統一】Rubyインタプリタは常に-wオプションをつけて実行すること

Always run the Ruby interpreter with the -w option so it will warn you if you forget either of the rules above!

Rubyには他の実行系と同様に冗長モード(verbose mode)というものがありますが、-wはバージョン情報などを除いた警告メッセージを表示します(参考: Rubyリファレンスマニュアル)。

-w-vのようなわかりきったバージョン情報を表示しません。

2-48【統一】メソッド定義はネストしないこと(ネスト定義が必要ならlambdaを使う)

Do not use nested method definitions, use lambda instead.
Nested method definitions actually produce methods in the same scope(e.g. class) as the outer method. Furthermore, the “nested method” will be redefined every time the method containing its definition is invoked.

  • 理由1: メソッド定義はネストされていてもいなくても、生成されるときのスコープは変わらない
  • 理由2: ネストされたメソッドは、それを含むメソッドが呼び出されるたびに再定義されてしまう
# 不可
def foo(x)
  def bar(y) 
    # (略)
  end

  bar(x)
end

# 良好 - スコープは上と同じだが、#fooが呼び出されるたびに#barが再定義されずに済む
def bar(y)
  #(略)
end

def foo(x)
  bar(x)
end

# これでもよい
def foo(x)
  bar = ->(y) { 略 } # lambdaを使用
  bar.call(x)
end

解説

メソッド定義をネストしてもメソッドの名前空間は別にならない、つまりA::hoge::hugaのようなものにはならずにA::hogeA::hugaになるということですね。

morimorihogeさんが書いてくれた以下のコード例で動作を確認できます。

class A
  def hoge
    def huga
    end
  end
end

a = A.new
a.huga # NoMethodError
a.hoge # 動く
a.huga # 動く。一行手前のhogeで定義されたhugaが実行される
a.hoge
a.huga # 動く。二行前のhugaとは別の再定義されたhugaが実行される

# ↑#hogeを呼び出す度にhugaが再定義される

A#hugaは、A#hogeを一度実行するまでは未定義なのでエラーになり、A#hugaA#hogeを実行するたびに再定義されています。

kazzさんによると、Javaではそもそもメソッド定義のネストはできないそうです。また、lambdaが推奨されているのは、メソッド名を定義しないのでメソッド名の無用な衝突を避けられるからではないかとのことでした。

2-49【統一】 ブロックが1行のみなら、lambdaの略記->()を使い、ブロックが複数行なら略記でないlambdaメソッドを使う

Use the new lambda literal syntax for single line body blocks. Use the lambda method for multi-line blocks.

これも例によって「どちらが正しい」ではなく、どちらに統一するかという問題ですね。

# 不可
l = lambda { |a, b| a + b } # 1行ブロックではlambdaメソッドは使わない
l.call(1, 2)

# 間違ってはいないが、->()にdo〜endブロックを続けると非常に読みづらい
l = ->(a, b) do
  tmp = a * 7
  tmp * b / 50
end

# 良好
l = ->(a, b) { a + b }      # 1行ブロックなら略記->()と{ }ブロックで
l.call(1, 2)

l = lambda do |a, b|        # 複数行ブロックならlambdaとdo〜endブロックで
  tmp = a * 7
  tmp * b / 50
end

ブロックの使い分けについては、Rubyスタイルガイドを読む: 文法(5)で扱った以下のルールに従っています。

ブロック記法: 1行に収まる場合は原則{…}を使い、制御フローやメソッド定義では常にdo…endを使う

2-50【統一】lambdaの略記->() { }でパラメータがある場合は丸かっこ( )を省略しないこと

Don’t omit the parameter parentheses when defining a stabby lambda with parameters.

# 不可
l = ->x, y { something(x, y) }

# 良好
l = ->(x, y) { something(x, y) }

->x,yが別物に見えてしまいそうなので、丸かっこが必要なのは納得です。

2-51【統一】lambdaの略記->() { }でパラメータがない場合は丸かっこ( )を省略すること

Omit the parameter parentheses when defining a stabby lambda with no parameters.

上と対をなすルールですね。読みやすさというよりタイプ量を減らすのが目的のようにも思えます。

# 不可
l = ->() { something }

# 良好
l = -> { something }

2-52【統一】 Proc.newは避け、procを使う

Prefer proc over Proc.new.

# 不可
p = Proc.new { |n| puts n }

# 良好
p = proc { |n| puts n }

以下で説明されているようにProc.new#procに違いはないので、純粋にスタイル上の理由で#procにしたということですね。

Kernel.#proc は Proc.new と同じになります。 引数に & を付けることで手続きオブジェクト化したブロックは、Proc.new で生成されたそれと 同じにように振る舞います。
Rubyリファレンスマニュアルより

2-53【統一】lambdaやprocの実行にはproc.call()を使う: proc[]proc.()は避ける

Prefer proc.call() over proc[] or proc.() for both lambdas and procs.

# 不可 
l = ->(v) { puts v }
l[1]      # enumerationのアクセスに見えるのでよくない

# これも不可
l = ->(v) { puts v }
l.(1)     # マイナーすぎる

# 良好
l = ->(v) { puts v }
l.call(1) # この記法がおすすめ

proc[]proc.()で呼べるということ自体、初めて知りました。

2-54【統一】 使わないブロックパラメータ名やローカル変数名の冒頭には_を付ける(_のみでもよい)

Prefix with _ unused block parameters and local variables. It’s also acceptable to use just _ (although it’s a bit less descriptive). This convention is recognized by the Ruby interpreter and tools like RuboCop and will suppress their unused variable warnings.

多重代入の変数や仮引数で使わない変数は、_変数か、_で始まる変数名にするのがRubyのスタイルです。こうすることで、その変数がその後使われないことを示せます。

Ruby実行系やRuboCop(文法チェック)は、_変数や、_で始まる変数については「変数が未使用」警告を表示しません。

# 不可
result = hash.map { |k, v| v + 1 }

def something(x)
  unused_var, used_var = something_else(x)
  # (略)
end

# 良好
result = hash.map { |_k, v| v + 1 }

def something(x)
  _unused_var, used_var = something_else(x)  #  _unused_varは以後使われないことがわかる
  # (略)
end

# 良好
result = hash.map { |_, v| v + 1 }

def something(x)
  _, used_var = something_else(x)            # _は以後使われないことがわかる
  # (略)
end

2-55【統一】標準入出力は$stdout/$stderr/$stdinに統一する

Use $stdout/$stderr/$stdin instead of STDOUT/STDERR/STDIN.
STDOUT/STDERR/STDIN are constants, and while you can actually reassign (possibly to redirect some stream) constants in Ruby, you’ll get an interpreter warning if you do so.

STDOUT/STDERR/STDINは定数なので、再代入するとRubyで警告が表示されます。

2-56【統一】$stderr.putsは避け、#warnを使うこと

Use warn instead of $stderr.puts. Apart from being more concise and clear, warn allows you to suppress warnings if you need to (by setting the warn level to 0 via -W0).

#warnにしておくことで、Rubyの起動オプションで警告レベルを設定できるようになるからだそうです。たとえば-W0オプションをつけてRubyを実行すると、警告レベルがゼロになり、警告が表示されなくなります。

2-57【統一】String#%は避け、sprintfまたはformatを使うこと

Favor the use of sprintf and its alias format over the fairly cryptic String#% method.

formatsprintfの別名だそうです。

# 不可
'%d %d' % [20, 10]
# => '20 10'

# 良好
sprintf('%d %d', 20, 10)
# => '20 10'

# 良好
sprintf('%{first} %{second}', first: 20, second: 10)
# => '20 10'

format('%d %d', 20, 10)
# => '20 10'

# 良好
format('%{first} %{second}', first: 20, second: 10)
# => '20 10'

Strings#%」は、書式がシンプルな場合以外は避けるほうがよさそうです。

今回はここまでとします。次回の文法編は#joinなどのメソッドです。ご期待ください。

関連記事


Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ