user3597950
user3597950

Reputation: 10211

blocks in silly blocks rspec testing

I had the following tests given to me as an exercise:

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

I was able to solve them with the following code:

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

However I some of these concepts I do not understand all that well. For example:

  1. What exactly does the & symbol in the &block parameter mean?
  2. Similarly what is block.call and where is the actual block object I am assuming its calling?
  3. Could I theoretically use another method on block if I wanted to achieve something else?
  4. Also where can I learn a bit more about blocks

This exercise was a bit above my current knowledge.

Upvotes: 2

Views: 1628

Answers (1)

Patrick Oscity
Patrick Oscity

Reputation: 54684

  1. It means "this is the block parameter". You are not bound to calling it &block, so there needs to be a way to separate it from the other arguments. The same notation is used to pass arguments to a function as block as opposed to normal arguments (see below)

  2. block.call is exactly the same thing as yield. The difference is that you can use block to access the block itself without calling it immediately. For example, you could store the block for later execution. This is a common pattern known as lazy evaluation.

  3. Yes, you can also pass different things than a do/end block as the &block parameter. See below for some examples.

  4. @UriAgassi gave you an excellent link.

Here are some other things you can pass as block argument. First, just a simple method that takes a block for demonstration:

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

You can now pass a standard block

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

Or, in alternative block syntax, used for inline style

reverser { "hello" }
#=> olleh

You can also pass a lambda or proc, which is similar to a block. By using the &block notation you can pass a variable as block argument:

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

Or, in alternative lambda syntax

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

You can even take an existing method and pass it as block argument here you can see the great advantage of blocks: They are evaluated when block.call is executed, not when the code is loaded. Here this means that the string will change every time accordingly.

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"

You can do cool stuff with this, for example:

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

But remember not to overdo it, Ruby is all about expressive and readable code. Use these techniques when they enhance your code, but use simpler ways if possible.

Finally, here is also an example of lazy evaluation:

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

Upvotes: 5

Related Questions