Ruby's yield feature in relation to computer science
Asked Answered
V

4

50

I recently discovered Ruby's blocks and yielding features, and I was wondering: where does this fit in terms of computer science theory? Is it a functional programming technique, or something more specific?

Ventilator answered 18/4, 2009 at 20:27 Comment(0)
M
106

Ruby's yield is not an iterator like in C# and Python. yield itself is actually a really simple concept once you understand how blocks work in Ruby.

Yes, blocks are a functional programming feature, even though Ruby is not properly a functional language. In fact, Ruby uses the method lambda to create block objects, which is borrowed from Lisp's syntax for creating anonymous functions — which is what blocks are. From a computer science standpoint, Ruby's blocks (and Lisp's lambda functions) are closures. In Ruby, methods usually take only one block. (You can pass more, but it's awkward.)

The yield keyword in Ruby is just a way of calling a block that's been given to a method. These two examples are equivalent:

def with_log
  output = yield # We're calling our block here with yield
  puts "Returned value is #{output}"
end

def with_log(&stuff_to_do) # the & tells Ruby to convert into
                           # an object without calling lambda
  output = stuff_to_do.call # We're explicitly calling the block here
  puts "Returned value is #{output}"
end

In the first case, we're just assuming there's a block and say to call it. In the other, Ruby wraps the block in an object and passes it as an argument. The first is more efficient and readable, but they're effectively the same. You'd call either one like this:

with_log do
  a = 5
  other_num = gets.to_i
  @my_var = a + other_num
end

And it would print the value that wound up getting assigned to @my_var. (OK, so that's a completely stupid function, but I think you get the idea.)

Blocks are used for a lot of things in Ruby. Almost every place you'd use a loop in a language like Java, it's replaced in Ruby with methods that take blocks. For example,

[1,2,3].each {|value| print value} # prints "123"
[1,2,3].map {|value| 2**value}    # returns [2, 4, 8]
[1,2,3].reject {|value| value % 2 == 0} # returns [1, 3]

As Andrew noted, it's also commonly used for opening files and many other places. Basically anytime you have a standard function that could use some custom logic (like sorting an array or processing a file), you'll use a block. There are other uses too, but this answer is already so long I'm afraid it will cause heart attacks in readers with weaker constitutions. Hopefully this clears up the confusion on this topic.

Menides answered 19/4, 2009 at 9:28 Comment(1)
Thanks, that makes a lot more sense, and it ties in more with what I've learned so far about blocks.Ventilator
J
6

There's more to yield and blocks than mere looping.

The series Enumerating enumerable has a series of things you can do with enumerations, such as asking if a statement is true for any member of a group, or if it's true for all the members, or searching for any or all members meeting a certain condition.

Blocks are also useful for variable scope. Rather than merely being convenient, it can help with good design. For example, the code

File.open("filename", "w") do |f|
  f.puts "text"
end

ensures that the file stream is closed when you're finished with it, even if an exception occurs, and that the variable is out of scope once you're finished with it.

A casual google didn't come up with a good blog post about blocks and yields in ruby. I don't know why.

Response to comment:

I suspect it gets closed because of the block ending, not because the variable goes out of scope.

My understanding is that nothing special happens when the last variable pointing to an object goes out of scope, apart from that object being eligible for garbage collection. I don't know how to confirm this, though.

I can show that the file object gets closed before it gets garbage collected, which usually doesn't happen immediately. In the following example, you can see that a file object is closed in the second puts statement, but it hasn't been garbage collected.

g = nil
File.open("/dev/null") do |f|
  puts f.inspect # #<File:/dev/null>
  puts f.object_id # Some number like 70233884832420
  g = f
end
puts g.inspect # #<File:/dev/null (closed)>
puts g.object_id # The exact same number as the one printed out above,
  # indicating that g points to the exact same object that f pointed to
Janicejanicki answered 19/4, 2009 at 4:58 Comment(2)
How does this ensure the file is closed when you're finished it? Ruby automatically closes it when the block ends and f falls out of scope?Shoelace
@Shoelace The File#open method, when called with a block, will close the created file handle once the block has finished.Kraft
M
2

I think the yield statement originated from the CLU language. I always wonder if the character from Tron was named after CLU too....

Mok answered 18/4, 2009 at 20:29 Comment(1)
To the original poster: apropros of Daniel's answer, you might want to google for "coroutine" -- this was the underlying "computer science" concept that CLU implemented using yield.Irremissible
S
0

I think 'coroutine' is the keyword you're looking for.

E.g. http://en.wikipedia.org/wiki/Yield

Yield in computing and information science:

  • in computer science, a point of return (and re-entry) of a coroutine
Sawicki answered 18/4, 2009 at 20:34 Comment(3)
Credit also to @itowlson, who simultaneously mentioned 'coroutine' in a comment on another answer.Sawicki
The usage of the yield keyword in Ruby has nothing whatsoever to do with the usual CS definition of yield. It's just a subroutine call. Indeed, you can just use call instead of yield, if you assign the anonymous block to a variable.Authority
This is not the usage in Ruby.Menides

© 2022 - 2024 — McMap. All rights reserved.