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
의 인스턴스를 반환합니다. 블록이 주어지지 않는 한.
yield
및 block_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의 향후 기사, 폐쇄 또는 기타에서 읽고 싶은 내용을 알려주십시오.