루비의 블록 및 수율


275

yield루비에서 블록과 그 작동 방식 을 이해하려고합니다 .

어떻게 yield사용 되나요? 내가 본 많은 Rails 응용 프로그램 yield은 이상한 방식으로 사용 되었습니다.

누군가 나에게 설명하거나 이해할 수있는 곳을 보여줄 수 있습니까?


2
컴퓨터 과학과 관련하여 Ruby의 수익률 기능에 대한 답변에 관심이있을 수 있습니다 . 그것은 당신과 다소 다른 질문이지만, 그 문제에 약간의 빛을 비출 수 있습니다.
Ken Bloom

답변:


393

예, 처음에는 약간 수수께끼입니다.

Ruby에서 메소드는 임의의 코드 세그먼트를 수행하기 위해 코드 블록을 수신 할 수 있습니다.

메소드가 블록을 예상하면 yield함수 를 호출하여 블록을 호출합니다 .

예를 들어 목록을 반복하거나 사용자 정의 알고리즘을 제공하는 데 매우 편리합니다.

다음 예제를 보자.

Person이름으로 초기화 된 클래스 를 정의하고 do_with_name호출 할 때 name수신 된 블록에 속성을 전달 하는 메소드를 제공합니다 .

class Person 
    def initialize( name ) 
         @name = name
    end

    def do_with_name 
        yield( @name ) 
    end
end

이를 통해 해당 메소드를 호출하고 임의 코드 블록을 전달할 수 있습니다.

예를 들어, 이름을 인쇄하려면 다음을 수행하십시오.

person = Person.new("Oscar")

#invoking the method passing a block
person.do_with_name do |name|
    puts "Hey, his name is #{name}"
end

인쇄 할 것 :

Hey, his name is Oscar

블록은 매개 변수로라는 변수를받습니다 name(NB는이 변수를 원하는대로 호출 할 수 있지만 호출하는 것이 좋습니다 name). 코드가 호출되면 yield이 매개 변수를 값으로 채 웁니다 @name.

yield( @name )

다른 작업을 수행하기 위해 다른 블록을 제공 할 수 있습니다. 예를 들어, 이름을 바꾸십시오.

#variable to hold the name reversed
reversed_name = ""

#invoke the method passing a different block
person.do_with_name do |name| 
    reversed_name = name.reverse
end

puts reversed_name

=> "racsO"

우리는 정확히 같은 방법 ( do_with_name)을 사용했습니다-그것은 다른 블록 일뿐입니다.

이 예는 사소한 것입니다. 더 흥미로운 사용법은 배열의 모든 요소를 ​​필터링하는 것입니다.

 days = ["monday", "tuesday", "wednesday", "thursday", "friday"]  

 # select those which start with 't' 
 days.select do | item |
     item.match /^t/
 end

=> ["tuesday", "thursday"]

또는 문자열 크기를 기준으로 사용자 정의 정렬 알고리즘을 제공 할 수도 있습니다.

 days.sort do |x,y|
    x.size <=> y.size
 end

=> ["monday", "friday", "tuesday", "thursday", "wednesday"]

이것이 당신이 그것을 더 잘 이해하는 데 도움이되기를 바랍니다.

BTW, 블록이 옵션 인 경우 다음과 같이 호출해야합니다.

yield(value) if block_given?

선택 사항이 아닌 경우 호출하십시오.

편집하다

@hmak는 다음 예제에 대해 repl.it을 작성했습니다. https://repl.it/@makstaks/blocksandyieldsrubyexample


그것이 어떻게 인쇄 racsO되는지 the_name = ""
Paritosh Piplewar

2
죄송합니다, 이름으로 초기화 인스턴스 변수입니다 "Oscar" (안 대답 매우 분명하다)
OscarRyz

이 같은 코드는 어떻습니까? person.do_with_name {|string| yield string, something_else }
f.ardelian

7
따라서 Javascript 용어로, 주어진 메소드에 콜백을 전달하고 호출하는 표준화 된 방법입니다. 설명 주셔서 감사합니다!
yitznewton

보다 일반적인 방식으로-블록은 전략 패턴에 대한 루비 "향상된"구문 설탕입니다. 일반적인 사용법은 다른 작업과 관련하여 무언가를 수행하는 코드를 제공하는 것입니다. 그러나 루비 향상은 블록을 사용하여 컨텍스트를 전달하는 DSL을 작성하는 것과 같은 멋진 것들에 길을 열어줍니다
Roman Bulgakov

25

Ruby에서 메소드는 일반 인수 외에 블록이 제공되는 방식으로 호출되었는지 확인할 수 있습니다. 일반적으로이 block_given?방법을 사용하여 수행 하지만 &최종 인수 이름 앞에 앰퍼샌드 ( )를 접두어로 추가하여 블록을 명시 적 Proc로 나타낼 수도 있습니다 .

블록으로 메소드를 호출 yield하면 필요한 경우 일부 인수를 사용하여 블록을 제어 (블록 호출) 할 수 있습니다. 다음을 보여주는이 예제 방법을 고려하십시오.

def foo(x)
  puts "OK: called as foo(#{x.inspect})"
  yield("A gift from foo!") if block_given?
end

foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How nice =)

또는 특수 블록 인수 구문을 사용하십시오.

def bar(x, &block)
  puts "OK: called as bar(#{x.inspect})"
  block.call("A gift from bar!") if block
end

bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How nice =)

블록을 트리거하는 다른 방법을 알고 있으면 좋습니다.
LPing

22

누군가가 여기에 진정으로 자세한 답변을 제공 할 가능성은 있지만, Robert Sosinski 의이 게시물 은 항상 블록, 프로세스 및 람다 사이의 미묘함에 대한 훌륭한 설명이라는 것을 알았습니다.

연결하려는 게시물이 루비 1.8과 관련이 있다고 생각합니다. 루비 1.9에서는 블록 변수가 블록에 국한된 것과 같은 일부 사항이 변경되었습니다. 1.8에서는 다음과 같은 것을 얻을 수 있습니다.

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"

1.9가 당신에게 줄 것입니다 :

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"

이 컴퓨터에는 1.9가 없으므로 위의 오류가있을 수 있습니다.


이 기사에 대한 훌륭한 설명은 모든 것을 제 스스로 알아내는 데 몇 달이 걸렸습니다. =)
maerics

나는 동의한다. 나는 그것을 읽을 때까지 설명 된 내용의 절반을 알지 못했다고 생각합니다.
TheIV

업데이트 된 링크도 404입니다. 웨이 백 머신 링크 는 다음과 같습니다 .
klenwell

@ klenwell 감사합니다. 링크를 다시 업데이트했습니다.
theIV

13

나는 당신이 이미 위대한 대답에 그런 식으로 일을하는 이유를 추가하고 싶었습니다.

어떤 언어에서 왔는지 모르지만 정적 언어라고 가정하면 이런 종류의 것이 익숙해 보일 것입니다. 이것은 자바에서 파일을 읽는 방법입니다

public class FileInput {

  public static void main(String[] args) {

    File file = new File("C:\\MyFile.txt");
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    DataInputStream dis = null;

    try {
      fis = new FileInputStream(file);

      // Here BufferedInputStream is added for fast reading.
      bis = new BufferedInputStream(fis);
      dis = new DataInputStream(bis);

      // dis.available() returns 0 if the file does not have more lines.
      while (dis.available() != 0) {

      // this statement reads the line from the file and print it to
        // the console.
        System.out.println(dis.readLine());
      }

      // dispose all the resources after using them.
      fis.close();
      bis.close();
      dis.close();

    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

전체 스트림 체인을 무시하고 아이디어는 이쪽

  1. 정리해야 할 리소스 초기화
  2. 자원 사용
  3. 그것을 청소하십시오

이것이 루비에서하는 방법입니다.

File.open("readfile.rb", "r") do |infile|
    while (line = infile.gets)
        puts "#{counter}: #{line}"
        counter = counter + 1
    end
end

전혀 다릅니다. 이걸 분해

  1. File 클래스에 리소스를 초기화하는 방법을 알려줍니다.
  2. 파일 클래스에게 무엇을해야하는지 알려주십시오
  3. 여전히 입력하고있는 자바 사람들을 비 웃으십시오. ;-)

여기서 1 단계와 2 단계를 처리하는 대신 기본적으로 다른 클래스에 위임합니다. 보시다시피, 작성해야하는 코드의 양이 급격히 줄어들어 읽기가 쉬워지고 메모리 누수 나 파일 잠금이 지워지지 않을 가능성이 줄어 듭니다.

이제는 자바에서 비슷한 것을 할 수없는 것과는 달리 실제로 사람들은 수십 년 동안 그것을 해왔습니다. 이것을 전략 패턴 이라고합니다 . 차이점은 블록이 없으면 파일 예제와 같은 간단한 방법으로 작성 해야하는 클래스 및 메소드의 양으로 인해 전략이 과도하게 사용된다는 것입니다. 블록을 사용하면 그렇게 간단하고 우아한 방식으로 코드를 구성하지 않는 것이 합리적입니다.

이것은 블록이 사용되는 유일한 방법은 아니지만 다른 것 (예 : form_for api에서 레일에서 볼 수있는 빌더 패턴)은 머리를 감싸면 무슨 일이 일어나고 있는지 분명해야합니다. 블록을 볼 때 일반적으로 메소드 호출이 원하는 것으로 가정하고 블록이 원하는 방식을 설명하는 것이 안전합니다.


5
이것을 조금 단순화하자 : File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end자바 사용자들 에게는 더욱 웃어 보자 .
Michael Hampton

1
@MichaelHampton, 몇 기가 바이트 길이의 파일을 읽은 후 웃습니다.
akostadinov 2019

@akostadinov 아니 ... 울고 싶어요!
Michael Hampton

3
@MichaelHampton 또는 더 나은 아직 : IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }(메모리 문제가 없음)
Fund Monica의 소송

12

이 기사 가 매우 유용하다는 것을 알았습니다 . 특히 다음 예제는

#!/usr/bin/ruby

def test
  yield 5
  puts "You are in the method test"
  yield 100
end

test {|i| puts "You are in the block #{i}"}

test do |i|
    puts "You are in the block #{i}"
end

다음 출력을 제공해야합니다.

You are in the block 5
You are in the method test
You are in the block 100
You are in the block 5
You are in the method test
You are in the block 100

따라서 기본적으로 yield루비를 호출 할 때마다 do블록 또는 내부 에서 코드를 실행합니다 {}. 매개 변수가 제공되면 매개 변수로 제공 yield됩니다.do 블록에 .

저에게는 이것이 do블록이 무엇을하고 있는지 실제로 이해 한 것은 이번이 처음이었습니다 . 기본적으로 함수가 반복 또는 함수 구성을 위해 내부 데이터 구조에 액세스 할 수있는 방법입니다.

따라서 레일에있을 때 다음을 작성하십시오.

respond_to do |format|
  format.html { render template: "my/view", layout: 'my_layout' }
end

(내부) 파라미터로 블록 respond_to을 생성 하는 기능을 실행합니다 . 그런 다음 이 내부 변수 에서 함수 를 호출 하면 명령 을 실행하기위한 코드 블록이 생성 됩니다. 참고 가 요청한 파일 형식 인 경우에만 얻을 것입니다. (기술 :이 함수 는 소스 에서 볼 수있는 것처럼 실제로 사용 하지 않지만 기능은 본질적으로 동일합니다. 이 질문 은 토론을 참조하십시오 .) 이것은 함수가 초기화를 수행 한 다음 호출 코드에서 입력을 가져 오는 방법을 제공합니다. 그런 다음 필요한 경우 처리를 계속하십시오.doformat.htmlrender.htmlblock.callyield

또는 다르게 말하면 익명 함수를 인수로 사용하여 자바 스크립트에서 호출하는 함수와 비슷합니다.


8

루비에서 블록은 기본적으로 어떤 방법 으로든 전달되고 실행될 수있는 코드입니다. 블록은 항상 메소드와 함께 사용되며 일반적으로 인수로 데이터를 공급합니다.

블록은 Ruby gem (Rail 포함)과 잘 작성된 Ruby 코드에서 널리 사용됩니다. 그것들은 객체가 아니므로 변수에 할당 할 수 없습니다.

기본 구문

블록은 {} 또는 do..end로 묶인 코드입니다. 일반적으로 중괄호 구문은 한 줄 블록에 사용해야하고 do..end 구문은 여러 줄 블록에 사용해야합니다.

{ # This is a single line block }

do
  # This is a multi-line block
end 

모든 메소드는 암시 적 인수로 블록을 수신 할 수 있습니다. 메소드 내의 yield 문에 의해 블록이 실행됩니다. 기본 구문은 다음과 같습니다.

def meditate
  print "Today we will practice zazen"
  yield # This indicates the method is expecting a block
end 

# We are passing a block as an argument to the meditate method
meditate { print " for 40 minutes." }

Output:
Today we will practice zazen for 40 minutes.

yield 문에 도달하면 meditate 메서드가 블록에 대한 제어를 생성하고 블록 내의 코드가 실행되고 제어가 메서드로 반환되어 yield 문 바로 다음에 실행이 다시 시작됩니다.

메소드에 yield 문이 포함 된 경우 호출시 블록을 수신 할 것으로 예상됩니다. 블록이 제공되지 않으면 yield 문에 도달하면 예외가 발생합니다. 블록을 선택적으로 만들고 예외가 발생하지 않도록 할 수 있습니다.

def meditate
  puts "Today we will practice zazen."
  yield if block_given? 
end meditate

Output:
Today we will practice zazen. 

메소드에 여러 블록을 전달할 수 없습니다. 각 방법은 하나의 블록 만 수신 할 수 있습니다.

http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html 에서 더 많은 것을 보십시오


이것은 블록과 수율이 무엇인지, 어떻게 사용하는지 실제로 이해하게하는 유일한 대답입니다.
Eric Wang

5

때로는 다음과 같이 "수율"을 사용합니다.

def add_to_http
   "http://#{yield}"
end

puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}

그래, 왜? Logger사용자가 필요하지 않은 경우 일부 작업을 수행하지 않아도되는 등의 많은 이유 가 있습니다. 당신은 그래도 설명해야합니다 ...
Ulysse BN

4

간단히 말하면, 생성 한 메소드가 블록을 가져 와서 호출 할 수 있습니다. yield 키워드는 구체적으로 블록의 'stuff'가 수행 될 지점입니다.


1

수확량에 대해 두 가지 점이 있습니다. 먼저, 여기에 많은 답변이 수율을 사용하는 방법으로 블록을 전달하는 다른 방법에 대해 이야기하지만 제어 흐름에 대해서도 이야기합시다. 블록에 여러 번 생성 할 수 있기 때문에 특히 관련이 있습니다. 예를 살펴 보겠습니다.

class Fruit
  attr_accessor :kinds

  def initialize 
    @kinds = %w(orange apple pear banana)
  end

  def each 
    puts 'inside each'
    3.times { yield (@kinds.tap {|kinds| puts "selecting from #{kinds}"} ).sample }
  end  
end

f = Fruit.new
f.each do |kind|
  puts 'inside block'
end    

=> inside each
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block

각 메소드가 호출 될 때 한 행씩 실행됩니다. 이제 3.times 블록에 도달하면이 블록이 3 번 호출됩니다. 수율을 호출 할 때마다 이 수율은 각 방법을 호출 한 방법과 관련된 블록에 연결됩니다. yield가 호출 될 때마다 클라이언트 코드에서 각 메소드의 블록으로 제어권을 리턴합니다. 블록 실행이 완료되면 3.times 블록으로 돌아갑니다. 그리고 이것은 3 번 발생합니다. 따라서 클라이언트 코드의 블록은 수율이 3 개의 개별 시간으로 명시 적으로 호출되므로 3 개의 개별 경우에 대해 호출됩니다.

두 번째 요점은 enum_for와 yield입니다. enum_for는 Enumerator 클래스를 인스턴스화하고이 Enumerator 객체는 yield에 응답합니다.

class Fruit
  def initialize
    @kinds = %w(orange apple)
  end

  def kinds
    yield @kinds.shift
    yield @kinds.shift
  end
end

f = Fruit.new
enum = f.to_enum(:kinds)
enum.next
 => "orange" 
enum.next
 => "apple" 

따라서 외부 반복자를 사용하여 종류를 호출 할 때마다 한 번만 yield를 호출합니다. 다음에 호출 할 때 다음 수율 등을 호출합니다.

enum_for와 관련하여 흥미로운 내용이 있습니다. 온라인 설명서에는 다음이 명시되어 있습니다.

enum_for(method = :each, *args)  enum
Creates a new Enumerator which will enumerate by calling method on obj, passing args if any.

str = "xyz"
enum = str.enum_for(:each_byte)
enum.each { |b| puts b }    
# => 120
# => 121
# => 122

심볼을 enum_for의 인수로 지정하지 않으면, 루비는 열거자를 수신자의 각 메소드에 연결합니다. 일부 클래스에는 String 클래스와 같은 각 메소드가 없습니다.

str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String

따라서 enum_for로 호출 된 일부 객체의 경우 열거 방법이 무엇인지 명시해야합니다.


0

항복 은 이름없는 블록으로 사용되어 메소드의 값을 리턴 할 수 있습니다. 다음 코드를 고려하십시오.

Def Up(anarg)
  yield(anarg)
end

하나의 인수가 할당 된 "Up"메소드를 작성할 수 있습니다. 이제이 인수를 할당하여 관련 블록을 호출하고 실행할 수 있습니다. 파라미터 목록 다음에 블록을 할당 할 수 있습니다.

Up("Here is a string"){|x| x.reverse!; puts(x)}

Up 메소드가 yield와 함께 yield를 호출하면 요청을 처리하기 위해 블록 변수로 전달됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.