Блоки в глупых блоках

В качестве упражнения мне дали следующие тесты:

require "silly_blocks"

describe "some silly block functions" do

  describe "reverser" do
    it "reverses the string returned by the default block" do
      result = reverser do
        "hello"
      end
      result.should == "olleh"
    end

    it "reverses each word in the string returned by the default block" do
      result = reverser do
        "hello dolly"
      end
      result.should == "olleh yllod"
    end
  end

  describe "adder" do
    it "adds one to the value returned by the default block" do
      adder do
        5
      end.should == 6
    end

    it "adds 3 to the value returned by the default block" do
      adder(3) do
        5
      end.should == 8
    end
  end

  describe "repeater" do
    it "executes the default block" do
      block_was_executed = false
      repeater do
        block_was_executed = true
      end
      block_was_executed.should == true
    end

    it "executes the default block 3 times" do
      n = 0
      repeater(3) do
        n += 1
      end
      n.should == 3
    end

    it "executes the default block 10 times" do
      n = 0
      repeater(10) do
        n += 1
      end
      n.should == 10
    end

  end

end

Я смог решить их с помощью следующего кода:

def reverser
k = []
  x = yield.split(" ")
  x.each do |y|
    n = y.reverse
  k.push(n)
end

m = k.join(" ")
m
end

def adder(num=1, &block)
  block.call + num
end

def repeater(num=1, &block)
    for i in (1..num) do
        block.call
    end
end

Однако я некоторые из этих концепций не очень хорошо понимаю. Например:

  1. Что именно обозначает символ & в параметре &block?
  2. Точно так же, что такое block.call и где находится реальный объект блока, который я предполагаю для его вызова?
  3. Могу ли я теоретически использовать другой метод на блоке, если я хочу достичь чего-то еще?
  4. Также, где я могу узнать немного больше о блоках

Это упражнение было немного выше моих нынешних знаний.

1 ответ

Решение
  1. Это означает "это параметр блока". Вы не обязаны называть это &blockпоэтому должен быть способ отделить его от других аргументов. Та же запись используется для передачи аргументов функции в виде блока, в отличие от обычных аргументов (см. Ниже).

  2. block.call это то же самое, что yield, Разница в том, что вы можете использовать block получить доступ к самому блоку, не вызывая его немедленно. Например, вы можете сохранить блок для последующего выполнения. Это распространенная модель, известная как ленивая оценка.

  3. Да, вы также можете передать разные вещи, чем do/end блок как &block параметр. Ниже приведены некоторые примеры.

  4. @UriAgassi дал вам отличную ссылку.

Вот некоторые другие вещи, которые вы можете передать в качестве аргумента блока. Во-первых, просто простой метод, который принимает блок для демонстрации:

def reverser(&block)
  block.call.reverse
end

Теперь вы можете передать стандартный блок

reverser do
  "hello"
end
#=> "olleh"

Или, в альтернативном синтаксисе блока, используется для встроенного стиля

reverser { "hello" }
#=> olleh

Вы также можете передать лямбда или процесс, который похож на блок. Используя обозначение & block, вы можете передать переменную в качестве аргумента блока:

my_block = lambda { "hello world!" }
reverser(&my_block)
#=> "!dlrow olleh"

Или в альтернативном лямбда-синтаксисе

my_block = -> { "hello world!" }
reverser(&my_block)
#=> "!dlrow olleh"

Вы даже можете взять существующий метод и передать его в качестве аргумента блока, здесь вы можете увидеть большое преимущество блоков: они оцениваются при выполнении block.call, а не при загрузке кода. Здесь это означает, что строка будет меняться каждый раз соответственно.

def foobar
  "foobar at #{Time.now}"
end
reverser(&method(:foobar))
#=> "0020+ 15:42:90 02-50-4102 ta raboof"
#=> "0020+ 31:52:90 02-50-4102 ta raboof"

Вы можете делать классные вещи с этим, например:

[1, 2, 3].each(&method(:puts))
1
2
3
#=> [1, 2, 3]

Но помните, чтобы не переусердствовать, Ruby - это выразительный и читаемый код. Используйте эти методы, когда они улучшают ваш код, но по возможности используйте более простые способы.

Наконец, вот еще пример ленивой оценки:

class LazyReverser
  def initialize(&block)
    @block = block
  end

  def reverse
    @block.call.reverse
  end
end

reverser = LazyReverser.new do
  # some very expensive computation going on here,
  # maybe we do not even need it, so lets use the
  # lazy reverser!

  "hello dolly"
end

# now go and do some other stuff

# it is not until later in the program, that we can decide
# whether or not we even need to call the block at all
if some_condition
  reverser.reverse
  #=> "yllod olleh"
else
  # we did not need the result, so we saved ourselves
  # the expensive computation in the block altogether!
end
Другие вопросы по тегам