An Introduction to Yield in Ruby, and filtering class instances
Tags: ruby, beginners
Published: 2019-09-06 06:21:06

##Keep your code DRY by avoiding WET##

Everyone that’s been programming for a while knows the value of keeping your code DRY (Don’t Repeat Yourself). The last thing that you want to do is have WET (Written Everything Twice) code. That’s right, it’s such an important concept that there’s *two* acronyms for it!

There are many benefits to keeping code as DRY as possible.

* It’s usually faster to turn a chunk of repeated code into a method, that way the next time you have to call it, you can do so in one line.

* If you need to change your code, you only need to do so in one spot.

* Your code is far easier to understand if every section is unique. Otherwise you risk reviewers looking at a section, saying ‘I’ve seen this before’, and not noticing that *this time* it’s a tiny bit different.

So it’s always good practice to put any repeated code into a method that can be called repeatedly. But what about those times when you *do* want to change your code block very slightly? For those instances, you have the *yield* keyword.

##How Yield Works##

The yield keyword allows you to temporarily pass control from the function in the middle of its execution. Essentially, you can build a function that has a *yield* in it, and then *you can replace that ‘yield’ with code of your choice every time you call the function*.

When you call a function with a yield, you include a block of code, surrounded by curly braces, that will replace any instance of the yield keyword. The following two functions have identical outputs.

```ruby
    def hello()
        return “Hello World!”
    end
    hello() #=> “Hello World!”


    def hello2()
        return yield
    end
    hello2(){“Hello World!”} #=> “Hello World!"
```

Multiple yields can be used in the same function, and each one will be replaced by the same code block.

```ruby
    def double()
        yield
        yield
    end
    double(){print “Hi!”} #=>”Hi!Hi!”
```

Of course, code blocks would be fairly useless without the ability to pass variables through the yield. In the below code snippet, the *name* variable is passed to the yield block, where it is called *person*.

```ruby
    def greet(name)
        print “You see #{name}. ”
        puts yield(name)
    end
    greet(‘Dave’){|person| “Hi #{person}!”} #=> ”You see Dave. Hi Dave!”
    greet(‘Dave’){|person| “#{person} says hello!”} #=> ”You see Dave.         
Dave says hello!”
```

You may have noticed in the last example that yield but life a bit easier for us. Notice that in the above two examples, we put the name at either the beginning or end of the second sentence. If we were passing the rest of the sentence into a function, we would need a bit of extra logic to determine where exactly the name should go. But thanks to yield giving us direct access to the variable inside the function, we can easily put it where we want.

##Functions vs Yield Blocks##

* The same functions can be called multiple times, but a yield block must be written in its entirety every time a function must yield. Therefore it is best practice to use the function for as much as possible, and only use yield for the mutable parts of the code.

* Yields and functions work identically for passed variables. Yield functions **only have access to the variables passed to it when it is called**, just like functions. Variables will be passed to yield in the default manner; this means that changing an integer, string, or float in a yield will not affect the value outside the yield; but changing an array or hash will.   

* The **return** keyword ends the functions that the yield is in. This means that anything that appears after the yield will not execute. This is something you’ll probably want to avoid in most cases, so just use implicit return.

* Within the function containing yield, you can treat yield as a function for the purposes and syntax of passing it values and retrieving values from it. For example, if a yield block takes in a string and returns an array, you can use *yield(string)[3]* to pass in the string and get the 4th element of that array. You can also set variables equal to the output of yield: *foo = yield*.

##Using Yield with Class Iterators##

One of the most common uses of yields is adapting classes for use in iterators. Imagine that we have a People class, and that each instance of this class has various attributes: name, age, career, etc.. If we wanted to make functions that could easily filter out specific people, we might have a few functions that look like this.

```ruby
    def filterPeopleByAge(minAge,maxAge)
        people = Person.all.filter do |person|
            person.age >= minAge && person.age <= maxAge
        end
    return people
    end
    filterPeopleByAge(18,26) 
    #Returns everyone between 18 and 26

    def filterPeopleByName(name)
        people = Person.all.filter do |person|
            person.first_name == name
        end
        return people
    end
    filterPeopleByName (‘Dave’) 
    #Returns everyone named Dave


    def filterPeopleByOccupation(occupation_array)
        people = Person.all.filter do |person|
            occupation_array.include?(person.occupation)
        end
        return people
    end
    filterPeopleByOccupation ([‘Doctor’,’Lawyer’,’Professor’]) 
    #Returns all doctors, lawyers, and professors
```

But that certainly seems like a lot of repeated code. We’re only changing a single thing across each of those functions: the filter condition. We can’t simply write a function that can handle searching across every possible attribute (at least, not one that’s tidy or easily legible). But by using a yield, we can directly control the condition statement. The function and yield below can perform the role of the above functions in a much shorter and more legible manner.

```ruby
    def filterPeople()
        people = Person.all.filter do |person|
            yield(person)
        end
        return people
    end
    filterPeople(){|person|person.age > 18 && person.age < 26} 
    # Returns all people between 18 and 26
    filterPeople(){|person|person.first_name = ‘Dave’} 
    #Returns all people whose first name is Dave
    filterPeople(){|person|         
    [‘Doctor’,’Lawyer’,’Professor’].include(person.occupation) } 
    #Returns all doctors, lawyers, and professors
```

This generic function is even more powerful than the previous set of functions for three reasons. First, it's still just as valid even if we add additional attributes to the person class. Second, it only takes one line to call and create new search criteria. And finally, we can easily use it to combine search criteria.

```ruby
    filterPeople() {|person| person.age < 40 && person.occupation == 'Lawyer'}
    #Returns all lawyers under 40
```

##An even MORE generic solution##

The previous search function looks pretty good on paper, but in practice it has a glaring flaw. Yield ONLY has access to values that are passed to it from the yielding function. 

```ruby
    def filterPeople(args)
        people = Person.all.filter do |person|
            yield(person,args)
        end
        return people
    end
    minAge = 18
    maxAge = 40
    filterPeople(){|person|person.age > minAge && person.age < maxAge}
    #THIS DOES NOT WORK. The yield cannot access the Age values because they were not passed in during the filterPeople function. 
```    

If we want to be able to have a smarter search function, we need to allow it to accept some parameters. We'll create a generic array *args* that we can use to hold our criteria. 

```ruby
    def filterPeople(args=nil)
        people = Person.all.filter do |person|
            yield(person,args)
        end
        return people
    end
```

Now we are freely capable of passing parameters to the yield through the function. And because the function and yield block are all called together, it should be fairly legible as to what args[foo] is referring to.

```ruby
    minAge = 14
    maxAge = 18
    filterPeople([minAge,maxAge]){|person,args|person.age > args[0] && person.age < args[1]}


    minAge = 30
    occupations = ['Artist','Race Car Driver','Athlete']
    filterPeople([minAge,occupations]){|person,args|person.age > args[0] 
    && args[1].include?(person.occupation)}
```

Now we have a function which allows us to create any arbitrary search criteria that we want for the Person class! And we can call it on a single line no less!