Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Ruby의 클로저:블록, 프로시저 및 람다

Ruby Magic에서 우리는 작동 방식을 이해하기 위해 우리가 매일 사용하는 것의 이면에 숨겨진 마법에 빠져드는 것을 좋아합니다. 이번 호에서는 블록, 프로시저 및 람다 간의 차이점을 살펴보겠습니다.

일류 함수를 사용하는 프로그래밍 언어에서 함수는 변수에 저장되고 다른 함수에 인수로 전달될 수 있습니다. 함수는 다른 함수를 반환 값으로 사용할 수도 있습니다.

클로저는 환경이 있는 일급 함수입니다. 환경은 클로저가 생성될 때 존재했던 변수에 대한 매핑입니다. 클로저는 다른 범위에 정의된 경우에도 이러한 변수에 대한 액세스를 유지합니다.

Ruby에는 일급 함수가 없지만 블록, 프로시저 및 람다 형식의 클로저가 있습니다. 블록은 코드 블록을 메서드에 전달하는 데 사용되며 프로시저와 람다에서는 코드 블록을 변수에 저장할 수 있습니다.

차단

Ruby에서는 차단 나중에 실행하기 위해 생성할 수 있는 코드 조각입니다. 블록은 do 내에서 블록을 생성하는 메소드로 전달됩니다. 및 end 키워드. 많은 예 중 하나는 #each입니다. 열거 가능한 개체를 반복하는 메서드입니다.

[1,2,3].each do |n|
  puts "#{n}!"
end
 
[1,2,3].each { |n| puts "#{n}!" } # the one-line equivalent.

이 예에서 블록은 Array#each에 전달됩니다. 배열의 각 항목에 대한 블록을 실행하고 콘솔에 인쇄하는 메서드입니다.

def each
  i = 0
  while i < size
    yield at(i)
    i += 1
  end
end

Array#each의 단순화된 예에서 , while 루프, yield 배열의 모든 항목에 대해 전달된 블록을 실행하기 위해 호출됩니다. 블록이 암시적으로 메서드에 전달되므로 이 메서드에는 인수가 없습니다.

암시적 블록 및 yield 키워드

Ruby에서 메서드는 암시적 및 명시적으로 블록을 사용할 수 있습니다. 암시적 블록 전달은 yield를 호출하여 작동합니다. 메소드의 키워드. yield 키워드는 특별합니다. 전달된 블록을 찾아 호출하므로 메서드가 허용하는 인수 목록에 블록을 추가할 필요가 없습니다.

Ruby는 암시적 블록 전달을 허용하므로 블록으로 모든 메서드를 호출할 수 있습니다. yield를 호출하지 않는 경우 , 블록은 무시됩니다.

irb> "foo bar baz".split { p "block!" }
=> ["foo", "bar", "baz"]

호출된 메서드가 하는 경우 yield, 전달된 블록을 찾아서 yield에 전달된 모든 인수와 함께 호출합니다. 키워드.

def each
  return to_enum(:each) unless block_given?
 
  i = 0
  while i < size
    yield at(i)
    i += 1
  end
end

이 예는 Enumerator의 인스턴스를 반환합니다. 블록이 주어지지 않는 한.

yieldblock_given? 키워드는 현재 범위에서 블록을 찾습니다. 이렇게 하면 블록을 암시적으로 전달할 수 있지만 변수에 저장되지 않으므로 코드에서 블록에 직접 액세스할 수 없습니다.

명시적으로 블록 전달

앰퍼샌드 매개변수(보통 &block라고 함)를 사용하여 인수로 블록을 추가하여 메서드에서 블록을 명시적으로 수락할 수 있습니다. ). 이제 블록이 명시적이므로 #call을 사용할 수 있습니다. yield에 의존하는 대신 결과 개체에 대한 직접 메서드 .

&block 인수는 적절한 인수가 아니므로 블록 이외의 다른 것으로 이 메서드를 호출하면 ArgumentError가 생성됩니다. .

def each_explicit(&block)
  return to_enum(:each) unless block
 
  i = 0
  while i < size
    block.call at(i)
    i += 1
  end
end

이렇게 블록이 전달되어 변수에 저장되면 자동으로 proc으로 변환됩니다. .

프로세스

"proc"은 Proc의 인스턴스입니다. 실행할 코드 블록을 보유하고 변수에 저장할 수 있는 클래스입니다. 프로시저를 생성하려면 Proc.new를 호출합니다. 블록을 전달하십시오.

proc = Proc.new { |n| puts "#{n}!" }

proc은 변수에 저장할 수 있으므로 일반 인수와 마찬가지로 메서드에 전달할 수도 있습니다. 이 경우 proc이 명시적으로 전달되므로 앰퍼샌드를 사용하지 않습니다.

def run_proc_with_random_number(proc)
  proc.call(random)
end
 
proc = Proc.new { |n| puts "#{n}!" }
run_proc_with_random_number(proc)

프로시저를 만들어 메서드에 전달하는 대신 앞에서 본 Ruby의 앰퍼샌드 매개변수 구문을 사용하고 대신 블록을 사용할 수 있습니다.

def run_proc_with_random_number(&proc)
  proc.call(random)
end
 
run_proc_with_random_number { |n| puts "#{n}!" }

메서드의 인수에 추가된 앰퍼샌드를 확인합니다. 이렇게 하면 전달된 블록을 proc 개체로 변환하고 메서드 범위의 변수에 저장합니다.

:어떤 상황에서는 메서드에 proc을 포함하는 것이 유용하지만 블록을 proc으로 변환하면 성능이 저하됩니다. 가능하면 암시적 블록을 대신 사용하십시오.

#to_proc

기호, 해시 및 메서드는 #to_proc를 사용하여 proc으로 변환할 수 있습니다. 행동 양식. 이것을 자주 사용하는 것은 기호에서 생성된 프로시저를 메서드로 전달하는 것입니다.

[1,2,3].map(&:to_s)
[1,2,3].map {|i| i.to_s }
[1,2,3].map {|i| i.send(:to_s) }

이 예는 #to_s를 호출하는 세 가지 동등한 방법을 보여줍니다. 배열의 각 요소에 첫 번째 기호에서는 앰퍼샌드가 접두사로 붙은 기호가 전달되며, 이 기호는 #to_proc를 호출하여 자동으로 proc으로 변환합니다. 방법. 마지막 두 개는 해당 프로시저가 어떻게 생겼는지 보여줍니다.

class Symbol
  def to_proc
    Proc.new { |i| i.send(self) }
  end
end

이것은 단순화된 예이지만 Symbol#to_proc의 구현은 후드 아래에서 무슨 일이 일어나고 있는지 보여줍니다. 이 메서드는 하나의 인수를 취하고 self를 보내는 proc을 반환합니다. 그것에. self 이후 이 컨텍스트에서 기호는 Integer#to_s를 호출합니다. 방법.

람다

람다는 본질적으로 몇 가지 구별되는 요소가 있는 procs입니다. 두 가지 면에서 "일반" 메서드와 비슷합니다. 즉, 호출될 때 전달된 인수 수를 적용하고 "정상" 반환을 사용합니다.

인수가 없는 람다를 호출하거나 예상하지 않는 람다에 인수를 전달하면 Ruby는 ArgumentError를 발생시킵니다. .

irb> lambda (a) { a }.call
ArgumentError: wrong number of arguments (given 0, expected 1)
        from (irb):8:in `block in irb_binding'
        from (irb):8
        from /Users/jeff/.asdf/installs/ruby/2.3.0/bin/irb:11:in `<main>'

또한 람다는 메서드와 동일한 방식으로 return 키워드를 처리합니다. proc을 호출할 때 프로그램은 proc의 코드 블록에 제어를 넘깁니다. 따라서 proc이 반환되면 현재 범위가 반환됩니다. 프로시저가 함수 내에서 호출되고 return을 호출하는 경우 , 함수도 즉시 반환됩니다.

def return_from_proc
  a = Proc.new { return 10 }.call
  puts "This will never be printed."
end

이 함수는 proc에 제어를 양보하므로 반환될 때 함수가 반환됩니다. 이 예제에서 함수를 호출하면 출력이 인쇄되지 않고 10이 반환됩니다.

def return_from_lambda
  a = lambda { return 10 }.call
  puts "The lambda returned #{a}, and this will be printed."
end

람다를 사용하면 인쇄됩니다. return 호출 람다에서 return을 호출하는 것처럼 작동합니다. 메소드에서 a 변수가 10으로 채워집니다. 라인이 콘솔에 인쇄됩니다.

블록, 절차 및 람다

이제 두 블록, 프로시저 및 람다에 대해 모두 살펴보았으므로 축소하고 비교를 요약해 보겠습니다.

  • 블록은 코드 비트를 함수에 전달하기 위해 Ruby에서 광범위하게 사용됩니다. yield를 사용하여 키워드를 사용하면 블록을 proc으로 변환하지 않고도 암시적으로 전달할 수 있습니다.
  • 앰퍼샌드가 접두사로 붙은 매개변수를 사용할 때 메서드에 블록을 전달하면 메서드 컨텍스트에서 proc이 발생합니다. 프로시저는 블록처럼 작동하지만 변수에 저장할 수 있습니다.
  • Lambdas는 메서드처럼 작동하는 프로시저입니다. 즉, 상위 범위 대신에 arity를 ​​적용하고 메서드로 반환됩니다.

이것으로 Ruby의 클로저에 대한 조사를 마칩니다. 어휘 범위 및 바인딩과 같은 클로저에 대해 더 배울 것이 있지만 향후 에피소드를 위해 이를 유지하겠습니다. 그동안 @AppSignal에서 Ruby Magic의 향후 기사, 폐쇄 또는 기타에서 읽고 싶은 내용을 알려주십시오.