Ruby Language
ブロックとProcsとLambdas
サーチ…
構文
- Proc.new( ブロック )
- ラムダ{| args |コード}
- - >(arg1、arg2){code}
- オブジェクト.to_proc
- {| single_arg |コード}
- do | arg、(key、value)| コード終了
備考
以下のように、複数のメソッドがチェーンされている場合、演算子の優先順位に注意してください。
str = "abcdefg"
puts str.gsub(/./) do |match|
rand(2).zero? ? match.upcase : match.downcase
end
代わりに何か印刷するabCDeFg
、あなたが期待するように、それは何か印刷します#<Enumerator:0x00000000af42b28>
-ので、これはされdo ... end
意味の方法よりも低い優先順位、持っているgsub
唯一見/./
引数をブロック引数ではありません。列挙子を返します。ブロックはputs
に渡され、 puts
は無視され、 gsub(/./)
結果が表示されます。
これを修正するには、 gsub
呼び出しをかっこでgsub
か、代わりに{ ... }
使用します。
Proc
def call_the_block(&calling); calling.call; end
its_a = proc do |*args|
puts "It's a..." unless args.empty?
"beautiful day"
end
puts its_a #=> "beautiful day"
puts its_a.call #=> "beautiful day"
puts its_a[1, 2] #=> "It's a..." "beautiful day"
最後の例のcall_the_block
メソッドをコピーしました。ここでは、ブロックでproc
メソッドを呼び出すことによってprocが作成されていることがわかります。メソッドのようなブロックも暗黙のリターンを持つことがわかります。つまり、procs(とlambdas)も同様です。 its_a
の定義では、ブロックは通常のものと同様にスプラット引数を取ることができます。彼らはデフォルトの引数を取ることもできますが、私はその方法を考えることができませんでした。最後に、複数の構文を使用してメソッドを呼び出すことができます。 call
メソッドか[]
演算子。
ラムダ
# lambda using the arrow syntax
hello_world = -> { 'Hello World!' }
hello_world[]
# 'Hello World!'
# lambda using the arrow syntax accepting 1 argument
hello_world = ->(name) { "Hello #{name}!" }
hello_world['Sven']
# "Hello Sven!"
the_thing = lambda do |magic, ohai, dere|
puts "magic! #{magic}"
puts "ohai #{dere}"
puts "#{ohai} means hello"
end
the_thing.call(1, 2, 3)
# magic! 1
# ohai 3
# 2 means hello
the_thing.call(1, 2)
# ArgumentError: wrong number of arguments (2 for 3)
the_thing[1, 2, 3, 4]
# ArgumentError: wrong number of arguments (4 for 3)
また、 ->
を使ってcreateと.()
を使ってlambda
the_thing = ->(magic, ohai, dere) {
puts "magic! #{magic}"
puts "ohai #{dere}"
puts "#{ohai} means hello"
}
the_thing.(1, 2, 3)
# => magic! 1
# => ohai 3
# => 2 means hello
ここでは、ラムダがprocとほとんど同じであることが分かります。ただし、いくつかの注意点があります。
ラムダの引数のアリティは強制されます。間違った数の引数をラムダに渡すと、
ArgumentError
ます。彼らはまだデフォルトのパラメータ、スプラットパラメータなどを持つことができますreturn
ながら、ラムダからラムダ戻る内からINGのreturn
囲む範囲外PROCリターンからINGの:def try_proc x = Proc.new { return # Return from try_proc } x.call puts "After x.call" # this line is never reached end def try_lambda y = -> { return # return from y } y.call puts "After y.call" # this line is not skipped end try_proc # No output try_lambda # Outputs "After y.call"
メソッドのブロック引数としてのオブジェクト
引数の前に&
(アンパサンド)を置くと、それがメソッドのブロックとして渡されます。オブジェクトは、 to_proc
メソッドを使用してProc
変換されます。
class Greeter
def to_proc
Proc.new do |item|
puts "Hello, #{item}"
end
end
end
greet = Greeter.new
%w(world life).each(&greet)
これはRubyの共通パターンであり、多くの標準クラスがそれを提供します。
たとえば、 Symbol
はto_proc
を引数に送信して実装します。
# Example implementation
class Symbol
def to_proc
Proc.new do |receiver|
receiver.send self
end
end
end
これは便利な&:symbol
イディオムを可能にします。これはEnumerable
オブジェクトでよく使われEnumerable
:
letter_counts = %w(just some words).map(&:length) # [4, 4, 5]
ブロック
ブロックは、中かっこ{}
(通常は単一行ブロック用)またはdo..end
(複数行ブロック用)で囲まれたコードのチャンクです。
5.times { puts "Hello world" } # recommended style for single line blocks
5.times do
print "Hello "
puts "world"
end # recommended style for multi-line blocks
5.times {
print "hello "
puts "world" } # does not throw an error but is not recommended
注意:中括弧はdo..end
より優先度が高い
収穫
ブロックは、ワードの使用方法及び機能内で使用することができるyield
:
def block_caller
puts "some code"
yield
puts "other code"
end
block_caller { puts "My own block" } # the block is passed as an argument to the method.
#some code
#My own block
#other code
yield
ブロックなしで呼び出すと、 LocalJumpError
ます。この目的のために、rubyはblock_given?
という別のメソッドを提供していますblock_given?
yieldを呼び出す前にブロックが渡されたかどうかを確認することができます
def block_caller
puts "some code"
if block_given?
yield
else
puts "default"
end
puts "other code"
end
block_caller
# some code
# default
# other code
block_caller { puts "not defaulted"}
# some code
# not defaulted
# other code
yield
はブロックにも引数をyield
ことができます
def yield_n(n)
p = yield n if block_given?
p || n
end
yield_n(12) {|n| n + 7 }
#=> 19
yield_n(4)
#=> 4
これは簡単な例ですが、 yield
は他のオブジェクトのコンテキスト内でインスタンス変数や評価への直接アクセスを許可するのに非常に便利です。例えば:
class Application
def configuration
@configuration ||= Configuration.new
block_given? ? yield(@configuration) : @configuration
end
end
class Configuration; end
app = Application.new
app.configuration do |config|
puts config.class.name
end
# Configuration
#=> nil
app.configuration
#=> #<Configuration:0x2bf1d30>
この方法でyield
を使用するとわかるように、 app.configuration.#method_name
。 app.configuration.#method_name
継続的に呼び出すよりも読みやすくなります。代わりに、コードを含むブロック内のすべての設定を実行することができます。
変数
ブロックの変数はブロックのローカル変数(関数の変数に似ています)で、ブロックが実行されると終了します。
my_variable = 8
3.times do |x|
my_variable = x
puts my_variable
end
puts my_variable
#=> 0
# 1
# 2
# 8
ブロックは保存できません。実行されるとブロックされます。ブロックを保存するには、 procs
とlambdas
を使用するprocs
ありlambdas
。
Procに変換する
to_proc
応答するオブジェクトは、 &
演算子を使用してprocsに変換できます(これもブロックとして渡すことができます)。
Symbolクラスは、 #to_proc
定義して、受け取ったオブジェクトの対応するメソッドをパラメータとして呼び出そうとします。
p [ 'rabbit', 'grass' ].map( &:upcase ) # => ["RABBIT", "GRASS"]
メソッドオブジェクトは#to_proc
も定義します。
output = method( :p )
[ 'rabbit', 'grass' ].map( &output ) # => "rabbit\ngrass"
部分的な塗布とカッシング
技術的には、Rubyには機能はありませんが、メソッドはありません。ただし、Rubyメソッドは他の言語の関数とほぼ同じように動作します。
def double(n)
n * 2
end
この通常のメソッド/関数は、パラメータn
とり、それを倍にして値を返します。次に、より高次の関数(またはメソッド)を定義しましょう:
def triple(n)
lambda {3 * n}
end
数値を返す代わりに、 triple
はメソッドを返します。 対話式Rubyシェルを使用してテストすることができます。
$ irb --simple-prompt
>> def double(n)
>> n * 2
>> end
=> :double
>> def triple(n)
>> lambda {3 * n}
>> end
=> :triple
>> double(2)
=> 4
>> triple(2)
=> #<Proc:0x007fd07f07bdc0@(irb):7 (lambda)>
実際に3倍の数を取得したい場合は、ラムダを呼び出す(または「減らす」)必要があります:
triple_two = triple(2)
triple_two.call # => 6
またはより簡潔に:
triple(2).call
カリングおよび部分的なアプリケーション
これは非常に基本的な機能を定義する上では有用ではありませんが、瞬時に呼び出されたり減らされたりしないメソッド/関数を使用したい場合に便利です。たとえば、番号を特定の数で追加するメソッドを定義するとします( add_one(2) = 3
)。あなたがこれらのトンを定義しなければならない場合、あなたはできます:
def add_one(n)
n + 1
end
def add_two(n)
n + 2
end
ただし、これを行うこともできます:
add = -> (a, b) { a + b }
add_one = add.curry.(1)
add_two = add.curry.(2)
ラムダ計算を使用して、 add
が(λa.(λb.(a+b)))
と言うことができます。 curryingは、 部分的に add
適用 add
方法です。そうadd.curry.(1)
である(λa.(λb.(a+b)))(1)
に低減することができる(λb.(1+b))
。部分的なアプリケーションは、 add
引数を1つ渡したが、後で提供するもう1つの引数を残したことを意味します。出力は特別な方法です。
より便利なカリングの例
特定の引数を指定すると、そこから特定の式を得ることができる、本当に大きな一般式があるとしましょう。この数式を考えてみましょう。
f(x, y, z) = sin(x\*y)*sin(y\*z)*sin(z\*x)
この式は3次元で作業するために作成されていますが、この式をyとzに関してのみ必要とします。また、xを無視するために、その値をpi / 2に設定したいとしましょう。最初に一般式を作ってみましょう:
f = ->(x, y, z) {Math.sin(x*y) * Math.sin(y*z) * Math.sin(z*x)}
さて、 yz
式を得るためにカリングを使ってみましょう:
f_yz = f.curry.(Math::PI/2)
次に、 f_yz
格納されているラムダを呼び出す:
f_xy.call(some_value_x, some_value_y)
これはかなり簡単ですが、 xz
の式を取得したいとしましょう。最後の引数でない場合、 y
をMath::PI/2
に設定するにはどうすればよいですか?まあ、もう少し複雑です:
f_xz = -> (x,z) {f.curry.(x, Math::PI/2, z)}
この場合、プレフィックスではないパラメータのプレースホルダを用意する必要があります。一貫性をf_xy
ため、 f_xy
ように書くことができます:
f_xy = -> (x,y) {f.curry.(x, y, Math::PI/2)}
f_yz
ラムダ計算がどのように機能するかはf_yz
です。
f = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x))))
f_yz = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x)))) (π/2) # Reduce =>
f_yz = (λy.(λz.(sin((π/2)*y) * sin(y*z) * sin(z*(π/2))))
今度はf_xz
見てみましょう
f = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x))))
f_xz = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x)))) (λt.t) (π/2) # Reduce =>
f_xz = (λt.(λz.(sin(t*(π/2)) * sin((π/2)*z) * sin(z*t))))
ラムダの計算についての詳細は、 これを試してみてください。