Git Product home page Git Product logo

yield-and-blocks's Introduction

Yield and Blocks

Objectives

  1. Understand how the yield keyword works in Ruby.
  2. Practice using yield with blocks.
  3. Gain a deeper understanding of the common iterator #each.

Calling a method with a block

A block is a bit of code enclosed in do/end keywords or curly brackets ({}). We call a method with a block by simply appending the block at the end of the method call. We've seen examples of how this is done with Ruby's enumerator methods like #each and #collect:

["Tim", "Tom", "Jim"].each do |name|
  puts "Hi, #{name}"
end

Here we are calling #each on our array of names. The #each method is being called with a block, the code between the do/end keywords. What's happening under the hood here is that #each uses a loop to access each element contained in our array in turn, passing — or yielding — the element to the block on each successive step of the iteration. Then inside the block, the value of the current element is stored in the name placeholder parameter, which allows us to write it to the screen as part of our greeting.

Let's take a look at another example. In the below snippet, we're writing a method that puts out every word in the array that starts with the letter "T":

["Tim", "Tom", "Jim"].each do |name|
  if name.start_with?("T")
    puts "Hi, #{name}"
  end
end

Once again, our #each method is yielding each element of the array to the accompanying block. The code in the block is executed, using each successive element from the array, as the iteration proceeds.

But how do #each and the other iterators like #collect actually pass, or yield, each successive element to the accompanying block? Under the hood, these methods rely on the yield keyword.

Let's take a closer look at yield and try to build our own custom methods that utilize it.

The yield keyword

The yield keyword, when used inside the body of a method, will allow you to call that method with a block of code and pass the torch, or yield, to that block. Think of the yield keyword as saying: "Stop executing the code in this method, and instead execute the code in this block. Then, return to the code in the method."

Let's look at the following example:

def yielding
  puts "the program is executing the code inside the method"
  yield
  puts "now we are back in the method"
end

To call this method with a block, we use the name of the method and append the block:

yielding { puts "the method has yielded to the block!" }

or:

yielding do
  puts "the method has yielded to the block!"
end

When we call #yielding with the above block, we will output:

the program is executing the code inside the method
the method has yielded to the block!
now we are back in the method

yielding with parameters

The yield keyword can take parameters. In other words, if you use yield and give it an argument, it will pass that argument to the block and that data will become available to the code in the block.

For example:

def yielding_with_arguments(num)
  puts "the program is executing the code inside the method"
  yield(num)
  puts "now we are back in the method"
end

We can call #yielding_with_arguments by providing both an argument and a block containing a placeholder, |i| in the following example, which will accept the argument passed to yield:

yielding_with_arguments(2) {|i| puts i * 3}

The |i| (placeholder variable in between pipes) is our placeholder for the yielded value so in this case, i equals 2. The puts i * 3 is the code we actually want to enact with our yielded value.

So, the above method call will output:

the program is executing the code inside the method
6
now we are back in the method

The syntax inside the block might look familiar — it is how we pass items from a collection into a block, one by one, when we use an iterator like #each.

Code-Along: Building our own method with yield

Let's revisit our earlier example of a call to the #each method that only puts out a greeting if the word we pass into the block starts with the letter "T".

["Tim", "Tom", "Jim"].each do |name|
  if name.start_with?("T")
    puts "Hi, #{name}"
  end
end

In this example, we'll be building our own method, #hello_t, that will recreate the functionality of #each.

Open up lib/hello.rb. We'll be coding the body of the #hello_t method here.

Step 1: Defining our method to accept an argument

Our method needs to operate on an array so let's define it to take in an argument:

# lib/hello.rb

def hello_t(array)
  # code here
end

Great, let's move on.

Step 2: Enacting an iteration

We know that we want our method to yield each element of the array successively to a block that we will call this method with. Let's use a while loop to create our iteration:

def hello_t(array)
  i = 0

  while i < array.length
    i = i + 1
  end
end
while loop review

In the code above, we set a counter variable, i, equal to 0. We start our while loop and tell it to execute the code in between the while and end keywords as long as i is less than the length of the array. Inside our while loop, we increment the value of i.

Now, as we iterate through the array, we need to yield each member of the array to the block that we'll pass to our #hello_t method when we call it.

Step 3: Yielding to the block

The first time through our while loop, i is equal to 0. The second time through the loop, i is 1, and so on. This will continue until i is equal to the index number of the last item in our array.

So, during each step of the while loop, i equals a given index number of our array. We can use this information to yield each successive value stored in the array to the passed-in block:

def hello_t(array)
  i = 0

  while i < array.length
    yield(array[i])
    i = i + 1
  end
end

Here, we use the bracket ([]) method to grab the value of each successive index element as we proceed through our while loop, yielding each to a block. Now we're ready to call our method.

Step 4: Calling our method

Let's call our method with a block, passing in our array of names as an argument:

hello_t(["Tim", "Tom", "Jim"]) do |name|
  if name.start_with?("T")
    puts "Hi, #{name}"
  end
end

Copy and paste the above method call into your lib/hello.rb file (paste it below the line that reads # call your method here!). Then, run the file with the ruby lib/hello.rb command in your terminal. You should see the following:

Hi, Tim
Hi, Tom

We're calling our method with the array of names as an argument and accompanying that method call with a block that accepts a |name| parameter. If the passed-in name begins with the letter "T", the block will puts out a greeting. Good job! Before moving on to Step 5, be sure to remove the above method call from lib/hello.rb.

Step 5: Passing our tests

Go ahead and run the test suite by typing learn test into your terminal in this lesson's directory. You'll see that we are already passing two of the tests but still have two to go. Looking at the first error, we see our test expects us to return the original array, but our method is currently returning nil:

Failures:

  1) #hello_t returns the original array
     Failure/Error:
       expect( hello_t(names){ |name| puts name } )
         .to eq(names)

       expected: ["Tim", "Tom", "Jim"]
            got: nil

       (compared using ==)
     # ./spec/hello_spec.rb:13:in `block (2 levels) in <top (required)>'

How can we fix this? We can tell our #hello_t method to return the original array:

def hello_t(array)
  i = 0

  while i < array.length
    yield(array[i])
    i = i + 1
  end

  array
end

Here, we tell our method to return the original array simply by having that array be the last line of the method. Whatever is evaluated last in a method will be the return value for the whole method. If you run the test again, you should now be passing three of the four tests.

Before we move on, let's take another look at our method call:

hello_t(["Tim", "Tom", "Jim"]) do |name|
  if name.start_with?("T")
    puts "Hi, #{name}"
  end
end

and compare it to the original version that uses #each:

["Tim", "Tom", "Jim"].each do |name|
  if name.start_with?("T")
    puts "Hi, #{name}"
  end
end

Note that we are doing the exact same thing in both cases; the only difference is which method we call. Just like our version, Ruby's #each method uses a loop to access each element of an array and yield it to whatever block we give it, then returns the original array at the end.

Advanced: Defining a method to optionally take a block

In the examples above, our methods will break if they are called without an accompanying block. You can see this for yourself if you add a method call at the bottom of hello.rb, this time without passing a block. If you do that and run ruby lib/hello.rb, you'll see a no block given (yield) (LocalJumpError). This is bad because we like our code to be flexible and accommodating. In other words, we don't want our code to break so easily and in such an ugly, uncontrolled manner.

We can fix this (and get our last test to pass) by refactoring our #hello_t method so that it can be called either with or without a block:

def hello_t(array)
  if block_given?
    i = 0

    while i < array.length
      yield(array[i])
      i = i + 1
    end

    array
  else
    puts "Hey! No block was given!"
  end
end

The block_given? method returns true if the method that contains block_given? is called with a block and false if it is not.

Our #hello_t method will yield each element of the array to the block if a block is present. Otherwise, it will puts out a helpful phrase.

Conclusion

You've already worked with enumerator methods like #each and #collect. These methods are called on collections, such as arrays. They take blocks as their arguments and yield each element of the collection to the block, allowing the code in the block to be applied to each element of the collection.

In the code-along above, we built our own implementation of the #each method. We used while to iterate through each element of the array and passed, or yielded, each successive element to an accompanying block. That block used a parameter placeholder, |name|, to set a variable, name, equal to whatever value is yielded into the block at each step of the iteration. That block also contained code to execute with each yielded element in turn.

Resources

yield-and-blocks's People

Contributors

annjohn avatar curiositypaths avatar deniznida avatar dougtebay avatar eclectic-coding avatar febbraiod avatar franknowinski avatar gj avatar ihollander avatar jmburges avatar joll59 avatar lizbur10 avatar lukegrecki avatar maxwellbenton avatar octosteve avatar onyoo avatar pickledyamsman avatar rrcobb avatar ruchiramani avatar sgharms avatar shmulim avatar sophiedebenedetto avatar timothylevi avatar victhevenot avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yield-and-blocks's Issues

tests incorrect

the yield block in the test code does not contain the if condition (if array.starts_with?("T")||array.starts_with?("t")).

typo

Need "its," not "it's" :)

But how do #each, and the other iterators like #collect, actually pass, or yield, each successive element to it's accompanying block? Under the hood, these methods rely on the yield keyword.

Yield and Blocks

Hello, the 3 links in Resources is not functional.

"Mix&Go - Mastering-ruby-blocks-in-less-than-5-minutes"

Thank you,
Emilia

rb file and rspec not on the same page

seems like even the solution branch's last commit refers to the inconsistency and it's been like this for two years now?
the concept is confusing and the mismatch doesn't help.

Resources links are broken

Thanks for raising this issue! Future learners thank you for your diligence. In
order to help the curriculum team address the problem, please use this template
to submit your feedback. We'll work on addressing the issue as soon as we can.

Please fill out as much of the information below as you can (it's ok if you
don't fill out every section). The more context we have, the easier it will be
to fix your issue!

Note: you should only raise issues related to the contents of this lesson.
If you have questions about your code or need help troubleshooting, reach out to
an instructor/your peers.


Link to Canvas

Add a link to the assignment in Canvas here.
https://learning.flatironschool.com/courses/1879/assignments/125406?module_item_id=259059

Describe the bug

Write a clear and concise description of what the bug is. Include the file and
line number(s) if possible.

What is the expected behavior?

All About Ruby (Links to an external site.) - Correct link
Ruby Blocks (Links to an external site.) - Correct link
Mix&Go (Links to an external site.) - Correct link
Mastering-ruby-blocks-in-less-than-5-minutes - link is broken

Write a clear and concise description of what you expected to happen.
Expecting to be redirected to the url

Screenshots

If applicable, attach screenshots to help explain your problem.

What OS are you using?

OS X

  • OS X
  • WSL
  • Linux

Any additional context?

Add any other context about the problem here.

hello_spec.rb discrepancy

There was a problem in the hello_spec.rb file. Initially the hello.rb file gives us this:

def hello

end

#call your method here

Yet when you pass this method an argument and iterate through the array correctly it still returns an error because the .spec file is testing for a hello_t method not a hello method, take a look.

Failures:

  1) #hello takes in an argument of an array and puts out a greeting to each person in the array whose name begins with the letter T
     Failure/Error: expect{hello_t(array){|name| puts "Hi, #{name}" if name.start_with?('T') }}.to output("Hi, Tim\nHi, Tom\n").to_stdout
     NoMethodError:
       undefined method `hello_t' for #<RSpec::ExampleGroups::Hello:0x007fc97c1822a0>

So what I did was I went into the hello.spec file and changed the test to look for a method named hello like the lab was started and not hello_t magiks.

Aside from that thanks for addressing the lack of yield before we hit the My_each, collect, select labs. Although I think now you're giving away how to complete the My_Each lab in the Yield and Blocks lab.

Broken resource link

The link for the article "Mix&Go - Mastering-ruby-blocks-in-less-than-5-minutes" comes up as a 404 not found error. I would submit a PR, but not sure if this should just be removed or if there's opportunity for it to be replaced with something else.

README.md - confusing wording "Your test should be passing" - requires more work in 'advanced'

Near the end of the README.md file, it says this:

"Here, we tell our method to return the original array simply by having that array be the last line of the method. Whatever is evaluated last in a method will be the return value for the whole method. If you run the test again, you should be passing."

The bold words are confusing, because if you run learn again, the individual test is passing but not the whole test.

The fact that the next part says 'ADVANCED' compounds the confusion. In all previous labs, this section was not required to complete all the learn tests. Yet in this one, it is.

I think this confuses the learner. It looks like the advanced section was added as a test later on, and README.md hasn't been changed to reflect that.

Rspec error

The test suite gives the wrong block which makes the test output always be "Hi, Tim\nHi, Tom\nHi, Jim" even if you define the method and code the block according to the README.

Codealong instructions don't match test

I know others have pointed this out, but it still seems that the "code along" exercise in this lesson does not correspond to the actual tests in the IDE. When they tell me it should pass, it doesn't because spec does not pass the correct block into my yield method.

README.md Error - |i| was not used. Creates confusion.

The final paragraph in README.md states this:

"In the code-along above, we built our own implementation of the #each method. We used while to iterate through each element of the array and passed, or yielded, each successive element to an accompanying block. That block used a parameter placeholder, |i|, to set a variable, i, equal to whatever value is yielded into the block at each step of the iteration. That block also contained code to execute with each yielded element in turn."

Yet, the code examples use |name|. This confuses the reader and may prevent learners from understanding the lesson.

forking issue

Fork and text aren't working, but submit learn did work

Code along doesn't pass with the code given in lesson

This lesson contains a code along lab. However, the code given to code along with during the lesson doesn't pass the tests. Initially, I assumed the error was mine—maybe I transposed two letters or made some other transcription error. But when I copied and pasted all of the code directly from the lesson to the IDE, it failed every test.

I took apart the code and modified it until it passed all of the tests—without altering the tests and still using yield—so I feel like I still met the larger objective. But I still wanted to point out that this is going to fail for other students.

Section Formatting

Hi there! This section is set up as a reading

screenshot1

but includes a code along, which is usually set up as a lab

screen shot 2015-11-25 at 10 53 04 am

I went ahead and selected "View on GitHub" to fork and clone, but thought I should still raise this issue in case changing it to a lab format makes it easier for others to understand. :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.