Ruby Language
블록과 Procs 및 Lambdas
수색…
통사론
- Proc.new ( 블록 )
- lambda {| args | 코드}
- -> (arg1, arg2) {code}
- object.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>
과 같이 인쇄합니다 #<Enumerator:0x00000000af42b28>
do ... end
는 메소드보다 우선 순위가 낮기 때문에 gsub
는 /./
인자 만 볼 수 있습니다. 블록 인수가 아닙니다. 열거자를 반환합니다. 블록은 puts
전달되어 무시되며 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
의 정의에서 블록은 일반적인 인수뿐만 아니라 splat 인수도 취할 수 있음을 알 수 있습니다. 그들도 기본 주장을 취할 수 있지만 그 방법을 생각할 수는 없습니다. 마지막으로, 당신은 메소드를 호출하기 위해 다중 구문을 사용할 수 있음을 볼 수 있습니다 - 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)
->
를 사용하여 람다를 생성하고 .()
를 사용하여 람다를 호출 할 수도 있습니다
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
여기에서 람다는 프로 시저와 거의 동일 함을 알 수 있습니다. 그러나 다음과 같은 몇 가지주의 사항이 있습니다.
람다의 논쟁의 원론은 강요된다. 잘못된 수의 인수를 람다에 전달하면
ArgumentError
합니다. 그들은 여전히 기본 매개 변수, 표시 매개 변수 등을 가질 수 있습니다.return
하면서, 람다에서 람다 반환 내에서 보내고return
바깥 쪽 범위 밖으로 PROC 반환에서 보내고 :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
이렇게하면 Enumerable
객체에서 일반적으로 사용되는 유용한 &:symbol
관용구를 사용할 수 있습니다.
letter_counts = %w(just some words).map(&:length) # [4, 4, 5]
블록
블록은 중괄호 {}
(일반적으로 한 줄 do..end
블록) 또는 do..end
(여러 줄 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
보다 우선 순위가 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
는 블록에 인수를 제공 할 수 있습니다.
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
계속 호출하는 것보다 코드를 더 쉽게 읽을 수 있습니다. 대신 코드가 포함 된 블록 내부의 모든 구성을 수행 할 수 있습니다.
변수
블록의 변수는 블록의 로컬 변수이며 (함수의 변수와 유사), 블록이 실행될 때 종료됩니다.
my_variable = 8
3.times do |x|
my_variable = x
puts my_variable
end
puts my_variable
#=> 0
# 1
# 2
# 8
블록을 저장할 수 없으며 실행되면 죽습니다. 블록을 저장하려면 procs
와 lambdas
를 사용해야합니다.
Proc로 변환
to_proc
응답하는 객체는 &
연산자를 사용하여 procs로 변환 될 수 있습니다.이 연산자는 블록으로 전달 될 수도 있습니다.
Symbol 클래스는 #to_proc
정의하므로 매개 변수로받은 객체에서 해당 메소드를 호출하려고합니다.
p [ 'rabbit', 'grass' ].map( &:upcase ) # => ["RABBIT", "GRASS"]
메소드 객체는 #to_proc
도 정의 #to_proc
.
output = method( :p )
[ 'rabbit', 'grass' ].map( &output ) # => "rabbit\ngrass"
부분 적용 및 커링
기술적으로 루비는 함수가 아니라 메소드를 가지고 있습니다. 그러나 Ruby 메서드는 다른 언어의 함수와 거의 동일하게 작동합니다.
def double(n)
n * 2
end
이 일반 메소드 / 함수는 매개 변수 n
취하여 두 배로하여 값을 반환합니다. 이제 고차 함수 (또는 메소드)를 정의합시다.
def triple(n)
lambda {3 * n}
end
숫자를 반환하는 대신 triple
은 메서드를 반환합니다. 대화 형 루비 셸을 사용하여 테스트 할 수 있습니다.
$ 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
예 : 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)))
라고 말할 수 add
. Currying은 부분적으로 add
적용 하는 방법이다. 그래서 add.curry.(1)
은 (λb.(1+b))
로 줄일 수있는 (λa.(λb.(a+b)))(1)
이다. 부분적인 애플리케이션은 하나의 인수를 add
하지만 다른 하나의 인수는 나중에 제공하도록 남겨 두었다는 것을 의미합니다. 출력은 특별한 방법입니다.
더 유용한 currying 예제들
우리가 정말 큰 일반 수식을 가지고 있다고 가정 해 봅시다. 특정 수식을 지정하면 특정 수식을 얻을 수 있습니다. 다음 수식을 고려하십시오.
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
공식을 얻기 위해 currying을 사용합시다.
f_yz = f.curry.(Math::PI/2)
그런 다음 f_yz
저장된 lambda를 호출합니다.
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 = -> (x,y) {f.curry.(x, y, Math::PI/2)}
다음은 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_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))))
람다 미적분에 대한 더 많은 정보를 얻으려면 이것을 시도 하십시오 .