Skip to content

Instantly share code, notes, and snippets.

@csigg
Last active December 12, 2015 04:48
Show Gist options
  • Save csigg/4716877 to your computer and use it in GitHub Desktop.
Save csigg/4716877 to your computer and use it in GitHub Desktop.
My most useful Block entry, I mean blog entry ever! ...seriously stop snickering. A bit on the usefulness of the Block. (v2)

Blocks

After learning a bit about Ruby and the basic way that data is stored and manipulated I came across the concept of the Block.

I was enthralled...and bewildered.

The concept seemed simple enough. Blocks are pieces of code, or instructions, that are passed to a function.

Amazing! Passing functions to other functions! This sounds highly advanced. I confess my first thought "Why would I ever want to pass a function to another function"?

This blog entry will begin with the syntax used to pass a Block to a function and finish with a discussion on the usefullness of being able to pass code to a function.

Syntax

So first a bit about Blocks in general. A Block is a bit of code that follows a function call and is contained within a set of braces {} or do end delimeters. There is a common convention that braces should be used where the Block is only one line and do enddelimeters are used where the code extends over multiple lines. Examples below...

some_function(arguments) { puts "Short Code Example" }
some_function(arguments) do 
  puts "This is a style"
  puts "commonly used for code in a block"
  puts "fitting over multiple lines"
end

Jim Weirich discusses another convention specific to the presence of a return value in his
blog entry.

Regardless of how the Block is contained it is always listed after a message on the same line as the message call.

Example 0:

object.message(arguments) {block}

Example 1:

3.each { puts "I can't stop printing!"}

Console...

I can't stop printing!
I can't stop printing!
I can't stop printing!

Example 2:

array = %w[ geography art science ] 
array.each do |subject| 
  puts "I love #{subject}"
end

Console:

I love art
I love science
I love math

The second example demonstrates how a Block can accept an argument passed to it from the function. In this case it is the subject or value of the array.

Usage

So a Block is a piece of logic that is passed to a function. But other than uitlizing simple pre-built iterators how can I unleash a Block within my own code? How can I write a function that accepts a Block?

Blocks are implicitly accepted into any function. The function can be written to accomodate the presence of a Block and default to a specific behavior if no Block is provided if block_given? To utilize a Block within a function the yield tag is used. When the function reaches yield the Block is invoked. If arguments are to be passed to the Block they are listed after yield. ( yield arguments)

def type_of_day
  if block_given?
    puts yield
  else
    puts "I havn't used a block all day, this code sucks"
  end
end

type_of_day { "I'm using a Block just like a real Ruby programmer!" }

So you can allow for there later use while still setting a default in case one isn't included in the message call.

Second... Blocks are particulary usefull when you find yourself writing lots of functions containing the same code. If you have five functions with code that differs only by one line... use a Block. Condense all of your functions into one well written function that contains a yield statement that accepts a Block with the differing code passed in.

Another similar example would be writing a function that contains a bit of logic that might change in the future. If you build a function that iterates through an array and performs a bit of logic on each index you could write a function that accepts a Block containing the logic to perform.

The following example shows the code that might exist for a program designed to automate yard maintenance. The Yard function has a public interface called maintain_garden that contains several functions needed for garden upkeep.

Example:

class Yard
  def maintain_garden
    water_garden
    weed_garden
    yield
  end
end

Utilizing a Block the maintain_garden function is dynamically able to increase its functionality by accepting additional messages in the form of a Block. If we decide to add functions like fertilize_garden and harvest_garden we can pass them into the function without having to modify its default behavior.

Example:

first_yard = Yard.new

### During the winter
first_yard.maintain_garden

### During the spring
first_yard.maintain_garden { fertilize_garden } 

### During the fall
first_yard.maintain_garden do 
  harvest_garden
  till_garden
end

During the spring and the fall the default behavior of the maintain_garden function is called with the added functionality specified by the Block listed after the function call.

Conclusion

Block's can be used to make your functions more versatile allowing you to reduce duplication of code
(making your code more DRY). They can also be utilized where logic may change in the future. Instead of having to go back and manipulate the code within a function a Block can be used to pass new logic to the existing function.

@cupakromer
Copy link

👍 Great job! This is excellent stuff, can't wait to get it up. I'm going to enter nitpick mode now, so don't take anything personally. 😄 Just want to help you with your message.

  • Global find and replace method with message. Being pedantic here.

    Ruby passes messages between objects

  • 4th paragraph, make the quote italic

  • Transition between 4th ( Amazing!... ) and 5th ( So first a bit... ) Needs another sentence or something to smooth out. Related, paragraph 5 seems to be related to the Syntax section. Consider adding a transition paragraph, then moving paragraph 5 to under the Syntax header.

  • Also, in paragraph 5 make do/end each look like a code tag.

  • Consider discussing (even using this link) using braces {} for blocks that return values. This provides semantic cues to intent of block. Also, chaining looks nicer:

    [1,2,3,4].select do |number|
        # complicated stuff
        number.odd?
    end.reduce(:+)
    
    [1,2,3,4].select{ |number|
        # complicated stuff
        number.odd?
    }.reduce(:+)

    But this also has to do with operator precedence (see also do end vs curly braces)

  • To use the block you list it after a method... should say, To pass a block, you start it after the message on the same line...

  • In the syntax section, make the console output a code block.

  • In the 1st console output section, the first Printing is capitalized, when it should be print lower case.

  • So a block is a piece of logic that is passed to a function. But other than uitlizing utilizing simple pre-built iterators how can I unleash a Block within my own code. How can I write a function thats takes a block. <== Missing some question marks here.

  • Get ride of the Well...

  • Note, that ALL methods take an implicit block. It's up to you to decide to use it.

  • type_of_day code sample is missing an end after the else statement

  • Indent spacing is off in the last two code samples.

  • Last two code samples are a bit long to show the usage of the block. It's not very clear when it's used. Trying making the example a little more terse.

  • spelling in 2nd to last paragraph versitile with versatile

  • Not sure about the last paragraph. Seems a bit jarring / out of context.

@brkane
Copy link

brkane commented Feb 6, 2013

I agree with Aaron on the last two code samples being a bit verbose. It was hard for me to see what changes were made; I had to step through and compare each line by line to see it. Something with a more dramatic change would be more....well...dramatic.
Personal style choice here, but I tend to shy away from future proofing against unknown requirements as a guiding principal. I prefer to wait until its time to DRY things and then make it generic. While the implementation/time cost might not be high (two lines not counting renaming the method) there is a cost in terms of pretty-ness/ease-of-read-y-ness:

# Looks polished:
puts "Total Jumps: " + count_jumps(array).to_s
# Bit more jarring:
puts "Total Jumps: " + count_jumps_using_block(array){|a, b| a + b}.to_s

It's balance decision to be made which depends on the context, but I usually try to shoot for pretty 😍.

@cupakromer
Copy link

Since this isn't a thesis, there isn't a need to explain the structure of the post, we'll see it easily from the headers. So I would instead say, replace the 5th paragraph This blog entry will begin...code to a function. with a quick summary of why you would want to pass a function (quickly addressing the statement prior).

Did you want to address Jim Weirich discusses another convention specific to the presence of a return value in his
blog entry.
that in any more detail?

In the usage section, what about explicit usage of blocks? I.e.

def my_method(&block)
  block.call 'always be learning'
end

Lastly, in your final example as written it will fail:

class Yard
  def maintain_garden
    water_garden
    weed_garden
    yield
  end
end

first_yard = Yard.new

### During the winter
first_yard.maintain_garden
# => example.rb:5:in `maintain_garden': no block given (yield) (LocalJumpError)
#       from example.rb:9:in `<main>'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment