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

[Ruby]クロージャーを使ってブロックを1回だけ実行する

こんにちは、hachi8833です。BPS Advent Calendarの合間を縫うように通常記事もお送りいたします。

高林哲さんの昔のブログ「Ruby: 一回だけ実行されるブロック」を読んでて、「Procかlambdaでクロージャを作れば、もう少し簡潔にできるのでは?」と思ったのでやってみました。

run_onceメソッド

ここではlambdaを使ってみました。

やってみると教科書のコードサンプルみたいな簡潔さです。アラートを最初の1回だけ表示するヘルパーメソッドなんかに使えそうです。

うれしい反面ちょっとさみしいので、YARD記法も足してみました。

コード

# Runs a block only once
# @param the block to run
# @return the object that is returned from the block first; nil after that
def run_once(&block)
  count = 1
  lambda do
    return if count == nil
    count = nil
    yield
  end
end
  • 当初、lambdaを略記法「->」で書いてましたが、後続のブロックが複数行なら略記ではなくlambdaと書くのがスタイルであるという指摘をmorimorihogeさんからいただいたので、改めました。
  • &blockによる明示的なブロック仮引数を使用するのは標準的なスタイルなので、省略しないようにしましょう。

参考: Rubyスタイルガイド: bbatsov/ruby-style-guide

使い方

a = run_once { puts "hello" }
a.call #=> "hello"
a.call #=> nil
a.call #=> nil
a.call #=> nil

応用: run_timesメソッド

上のコードを少し変えれば、渡した数値の回数だけブロックを実行し、以後は実行しないようにもできます。
デフォルト回数は1にしてみました。

コード

# Runs a block n times
# @param [num] (keyword arg) is the number of times to run the block, as well as a block to run
# @return the object that is returned from the block by the times specified; nil after that
def run_times(num: 1, &block)
  lambda do
    return if num < 1 
    num -= 1
    yield
  end
end

使い方

# キーワード引数で回数を渡す(引数のかっこは省略できない)
a = run_times(num:3) { puts "hello" }
a.call #=> "hello"
a.call #=> "hello"
a.call #=> "hello"
a.call #=> nil

callもなしにできればいいなと思いましたが、それは言わない約束ということで。

こぼれ話

クロージャ(closure)は、ブレスレットやネックレスなどを輪にして止めるときの方式・メカニズムを指すこともあるようです。closure rubyで検索してみて気が付きました。

追伸: リファクタリング

公開後、morimorihogeさんからリファクタリング案をいただきました。

  • run_timesの名前付き引数は冗長かも(ブロック以外の引数が1つしかないし、渡す値も明らかなので)
  • メソッドが2つになったのだから共通化してみては。
# リファクタリング後
def run_times(num, &block)
  lambda do
    return if num < 1 
    num -= 1
    yield
  end
end

def run_once(&block)
  run_times(1, &block)
end

関連記事


CONTACT

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