Поиск…


Синтаксис

  • Proc.new ( block )
  • lambda {| args | код}
  • -> (arg1, arg2) {code}
  • object.to_proc
  • {| single_arg | код}
  • do | arg, (ключ, значение) | конец кода

замечания

Будьте осторожны с приоритетом оператора, когда у вас есть линия с несколькими прикованными способами, например:

str = "abcdefg"
puts str.gsub(/./) do |match|
  rand(2).zero? ? match.upcase : match.downcase
end

Вместо того, чтобы печатать что-то вроде abCDeFg , как и следовало ожидать, оно печатает что-то вроде #<Enumerator:0x00000000af42b28> - это потому, что do ... end имеет более низкий приоритет, чем методы, а это значит, что gsub видит только аргумент /./ , а не аргумент блока. Он возвращает счетчик. Блок заканчивается передачей puts , который игнорирует его и просто отображает результат gsub(/./) .

Чтобы исправить это, либо заверните вызов gsub в круглые скобки, либо используйте { ... } .

процедура

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

Здесь вы можете видеть, что лямбда почти такая же, как и proc. Однако есть несколько предостережений:

  • Арктичность аргументов лямбды соблюдается; передавая неправильное количество аргументов в лямбда, поднимет ArgumentError . Они могут по-прежнему иметь параметры по умолчанию, параметры splat и т. Д.

  • 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"
    

Объекты как блок-аргументы для методов

Помещение символа & (амперсанд) перед аргументом передаст его как блок метода. Объекты будут преобразованы в Proc используя метод to_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

Это позволяет использовать полезную &:symbol идиому, обычно используемую с объектами Enumerable :

letter_counts = %w(just some words).map(&:length)  # [4, 4, 5]

Блоки

Блоки представляют собой куски кода, заключенного между фигурными скобками {} (обычно для однострочных блоков) или 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

Уступая

Блоки могут использоваться внутри методов и функций, используя 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? это позволяет проверить, прошел ли блок перед вызовом доходности

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 ING может быть очень полезным для обеспечения возможности прямого доступа к переменным экземпляра или оценки внутри контекста другого объекта. Например:

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 .

output = method( :p )

[ 'rabbit', 'grass' ].map( &output ) # => "rabbit\ngrass"

Частичное применение и каррирование

Технически Ruby не имеет функций, а методов. Однако метод Ruby почти идентичен функциям на другом языке:

def double(n)
  n * 2
end

Этот нормальный метод / функция принимает параметр n , удваивает его и возвращает значение. Теперь давайте определим функцию (или метод) более высокого порядка:

def triple(n)
  lambda {3 * n}
end

Вместо того, чтобы возвращать число, triple возвращает метод. Вы можете протестировать его с помощью Interactive Ruby Shell :

$ 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)>

Если вы хотите получить тройной номер, вам нужно позвонить (или «уменьшить») лямбда:

triple_two = triple(2)
triple_two.call # => 6

Или более кратко:

triple(2).call

Каррирование и частичное применение

Это не полезно с точки зрения определения очень простых функций, но полезно, если вы хотите иметь методы / функции, которые не вызываются или не вызываются мгновенно. Например, предположим, вы хотите определить методы, которые добавляют число по определенному числу (например, 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))) . Currying - это способ частичного применения add . Итак, add.curry.(1) , есть (λa.(λb.(a+b)))(1) которое можно свести к (λb.(1+b)) . Частичное приложение означает, что мы передали один аргумент для add но оставили другой аргумент, который будет предоставлен позже. Выход является специализированным методом.

Более полезные примеры каррирования

Допустим, у нас действительно большая общая формула, что, если мы укажем для нее некоторые аргументы, мы можем получить от нее конкретные формулы. Рассмотрим эту формулу:

f(x, y, z) = sin(x\*y)*sin(y\*z)*sin(z\*x)

Эта формула предназначена для работы в трех измерениях, но предположим, что мы хотим только эту формулу относительно y и z. Давайте также сказать, что для игнорирования x мы хотим установить его значение в pi / 2. Давайте сначала сделаем общую формулу:

f = ->(x, y, z) {Math.sin(x*y) * Math.sin(y*z) * Math.sin(z*x)}

Теперь давайте использовать currying для получения нашей формулы yz :

f_yz = f.curry.(Math::PI/2)

Затем, чтобы вызвать лямбду, сохраненную в f_yz :

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 = (λ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