수색…


통사론

  • 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에서 일반적인 패턴이며 많은 표준 클래스가이를 제공합니다.

예를 들어, Symbolto_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

블록을 저장할 수 없으며 실행되면 죽습니다. 블록을 저장하려면 procslambdas 를 사용해야합니다.

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 의 수식을 얻고 싶다고합시다. 마지막 인수가 아니라면 어떻게 yMath::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_xzf_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))))

람다 미적분에 대한 더 많은 정보를 얻으려면 이것을 시도 하십시오 .



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow