Tech Racho エンジニアの「?」を「!」に。
  • 開発

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などのメソッドです。ご期待ください。

関連記事



CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。