Rubyのクロージャを説明しました。

Rubyのクロージャを説明しました。Photo by Denise Johnson

他の言語からの「閉鎖」の概念を理解していない限り、最初はRubyで混乱していたかもしれません。 “Closure”は、Ruby(およびいくつかの関数型プログラミング言語)を除いて、時代錯誤です。

クロージャのメリアム-ウェブスターの定義は次のとおりです:

closure noun clo*sure\šklō-zhūr\archaic:囲む手段:enclosure

ここでも同じです:Rubyクロージャは”実行されるコードの束を囲みます”。 それは”囲い”です。 Wikipediaによると、クロージャは、ファーストクラスの関数を持つ言語で語彙的にスコープされた名前バインディングを実装するための技術です。

このスニペットを考えてみましょう:

2.times do topic = 'Ruby closures' puts "An adventure with #{topic}."end#=> An adventure with Ruby closures.#=> An adventure with Ruby closures.#=> 2

私たちはtopicputsdoendの間に”囲まれています。 その領域外のものはtopicにアクセスできませんが、putsメソッドは変数代入(名前バインド)と同じ環境(字句スコープ)内にあるため、putsメソッドはできます。 これは、クロージャと呼ばれるメソッドと変数を組み合わせるこの手法です。 メモ: { ... }do ... endは、メソッドに渡された場合と同等です。 私はそれらを交換可能に使用します。

このコードはどうですか?

lam = lambda do |topic| puts "An adventure with #{topic}."endlam.call('Ruby closures')#=> An adventure with Ruby closures

よく見ると、以前のスニペットと同じようなことをしています。 さて、最後のものはどうですか?

prok = Proc.new do |topic| puts "An adventure with #{topic}."endprok.call('Ruby closures')#=> An adventure with Ruby closures.

これらの形式は、Rubyでコードを囲む方法を表します。 前のコードスニペットでは、ブロック、ラムダ、Procがそれぞれ{}lambda {}Proc.new {}として表されています。 これらはRubyのクロージャです。 3つすべてで、ローカル変数topicを設定し、それをローカルメソッドputsに渡しました。 lamproktopic変数をどこに設定したのか疑問に思うかもしれません。 最初のコードスニペットで行ったように、明示的にtopic = "Ruby closures"として設定しませんでした。 その理由は、Proclambda#callメソッドがこれを行います。 あなたがProc#callのドキュメントを見ると、私たちは持っています:

call(params,…)→objInvokesブロックを呼び出し、ブロックのパラメータをparamsの値に設定します。

Rubyブロック(クロージャとして)について注目に値する微妙な違いは、Rubyではブロックをオブジェクトとして表すことができず、コードをグループ化する簡 それ自体では、彼らは非常に役に立たない。 一方、ProcsとLambdasは、一部の人が一流の市民と呼んでいるものです。

私は訂正される立場にありますが、先に述べたように、ブロック自体は何も意味しません。 これらは通常、メソッドに引数として渡されますが、コードをグループ化してその動作を変更するのにも役立ちます。 ここに例があります:

(1..10).each { |v| puts v if v.even? }# Output: ## 2# 4# 6# 8# 10

ここでは、範囲を反復してすべての偶数を出力していることが簡単にわかります。 さて、偶数で素数であるすべての数字を同時に印刷したい場合はどうなりますか? ここでは、ブロックがその有用性を披露するときです。 これを行うには、ブロック内のコードを変更するだけで取得できます:

require 'prime'(1..20).each { |v| puts v if v.even? && v.prime? }# => 2

ブロック内にあるものをグループ化することで、コードの動作を変更しました。 私たちはPrime標準ライブラリにprime?メソッドを使用するように要求しなければなりませんでした。 私たちが望むならば、素数を選ぶための私たちの方法を実装することもできました。 Rubyブロックを使用してコードをグループ化して列挙する方法を見てきました。 しかし、この記事では、閉鎖について話しています。 変数vに1から20の範囲の各数値を割り当てることにより、上記のブロックがクロージャとしてどのように機能するかをすでに見ていますか? そうでない場合は、ここでこのコードのビットはどうですか:

def shout(word, position) position.times { |x| puts "#{ x }: #{ word }"}endshout("SHOUTING!", 2)# Output: ## 0: SHOUTING!# 1: SHOUTING!

この例では、word変数を文字列SHOUTING!に割り当て、2番目のパラメータIntegerを変数positionに割り当て、xを使用して0からpositionまでの各数値を文字列を印刷する回数を表 閉鎖の定義を念頭に置いてください。 最後のコードはそれを満たしていますか?

ブロックについて少し知っていますが、これまでに示した例では、Rubyブロックを使用して動作をカプセル化し、Rubyのメソッドに渡しています。 また、ブロックを受け入れるメソッドを記述することもできます。

Rubyでは、yieldblock_given?のメソッドに恵まれています。 これらのメソッドは、ブロックを受け入れるメソッドを作成するのに役立ちます。 いつでもご参照8370>Rubyで、翻訳してこんにちはい渡されたブロックは”. block_given?はかなり直感的です。 このメソッドは、単に渡されたブロックがあったかどうかのブール値を返します。 ここでは、全体の概念を取得するための簡単な例です:

def shout yieldend# Call the #shout method with a block.shout { puts "SHOUTING!" }# => SHOUTING!

上記の方法を試して、yieldを2回または3回呼び出して、何が起こるかを確認してください。 これは何か意味がありますか? 呼び出したときにこのメソッドにブロックを渡さなかった場合、

ブロックが指定されていません(yield)(LocalJumpError)

あなたはそれを試すことができます。 それがblock_given?が便利な場所です。 ここでは、block_given?メソッドを使用する方法を説明します:

def shout block_given? ? yield : puts("Sir, you didn't pass any block.")end# Call the #shout method without a block.shout# => Sir, you didn't pass any block.

それはそれにあるほとんどすべてです! しかし、あなたはこのすべての実用的な使い方が何であるか疑問に思うかもしれません:かなりオブジェクトの初期化! このように:

module Genus class Species attr_accessor :name, :height, :weight, :ethnicity def initialize yield self if block_given? end endendhuman = Genus::Species.new do |characteristics| characteristics.name = "Emmanuel Hayford" characteristics.height = 188 # cm characteristics.weight = 104 # kg characteristics.ethnicity = "black"endp human.ethnicity# => "black"

私たちはcharacteristics変数に(種クラスのインスタンス化されたオブジェクトである)selfを設定しています。

Rubyのすべてがオブジェクトであっても、ブロックやメソッドなどの構成要素はそうではありません。 しかし、彼らはProcsを使ってオブジェクトに変えることができます。 したがって、主にProcsは、メモリ上でメソッドを呼び出したり、メモリに割り当てることができるオブジェク

最初の例をリファクタリングしてProcを使用しましょう。

だから、我々はこのコードのビットを持っています:

2.times do topic = 'Ruby closures' puts "An adventure with #{topic}."end

これに変更:

prok = Proc.new { topic = 'Ruby closures' puts "An adventure with #{topic}."}# `Proc.new` is equivalent to `proc`2.times do prok.callend# => An adventure with Ruby closures.# => An adventure with Ruby closures.

procはオブジェクトになったため、callメソッドを呼び出してブロックを返すことができます。BlockまたはProcオブジェクトに引数を取らせることで、これをより動的にするためにさらにリファクタリングすることができます:

prok = Proc.new { |topic| puts "An adventure with #{topic}."}2.times do prok.call('Ruby closures')end# => An adventure with Ruby closures.# => An adventure with Ruby closures.

ここに興味深い何かがあります; たとえば、単一の引数を取るProcオブジェクトの場合、いくつかの異なる方法で呼び出すことができます。 Rubyではcallの呼び出しを省略できます。 したがって、上記のコードの6行目は次のようになります:

prok.('Ruby closures')prok === 'Ruby closures'。ただし、複数の引数を使用したい場合は、引数を配列の要素としてProcオブジェクトに渡すことができます。 これが私たちのコードであると仮定しましょう:

prok = Proc.new { |x, y| puts x + y }

prokを呼び出すための以下のすべてのメソッドが有効です:

prok.call prok === prok# => 10# => 10# => 10

上記はすべてlambdaに有効です。

ラムダは、arityを尊重し、returnキーワードを少し違った扱いをするProcオブジェクトです。

ProcsとLambdasについて注目に値する動作は次のとおりです。 次の点を考慮してください:

cool_lambda = lambda { |first_name, nationality| puts "#{first_name} is #{nationality}."}cool_proc = proc { |first_name, nationality| puts "#{first_name} is #{nationality}."}cool_lambda.call("Matz", "Japanese")cool_proc.call("Aaron", "American")

すべてが期待どおりに動作します。 cool_lambdaの署名は2つの引数を受け入れますが、それについては厳密ですが、1つ以上または1つ少ない引数を渡した場合、次のようなもので爆発します:

引数の数が間違っています(指定された3、期待される2)(ArgumentError)

cool_proc 一方、ブロックが指定したことを行い、余分なparamsまたはその不在を黙って無視します。

クロージャを持つreturnキーワードの場合は次のとおりです:

class ClosureExperiment def call_closure(closure) puts "Calling #{ check_closure_type(closure) }." closure.call puts "#{ check_closure_type(closure) } got called!" end def check_closure_type(unknown_closure) unknown_closure.lambda? ? "Lambda" : "Proc" endendclosure = ClosureExperiment.new.call_closure(lambda { return })# => Calling Lambda.# => Lambda got called!closure = ClosureExperiment.new.call_closure(proc { return })# => Calling Proc.

上記のことから、Procsはreturnを見るとすぐにスナップしますが、Lambdaはコンテキスト内のすべてが実行されるまで躊躇します。

これら二つのクロージャのもう一つの違いは、ラムダがデフォルトの引数で定義され、配列を渡された場合、余分な引数がない限りProcはそれを無視し:

lam = lambda { |a, b = :foo| p a, b }.call()# => , :foo]prok = proc { |a, b = :foo| p a, b }.call()# => prok = proc { |a, b = :foo| p a, b }.call()# => 

それはProcsとLambdasがどのように異なるかのためです。

結論として、私はProcsをオブジェクトとして表されるブロックと考えるのが好きですが、Lambdaは間違った数の引数を渡すとエラーが発生するProcsの変種で

Ruby、JavaScript、web技術を探求するためにTwitterで私に従ってください。 私の時事通信を予約購読するか、またはの私の内容有用見つけたら私にコーヒーを買うことを考慮しなさい。

コメントを残す

メールアドレスが公開されることはありません。