Why Does the Break Statement in Ruby Behave Differently When Using Proc.New V. the Ampersand Sign

Why does the break statement in ruby behave differently when using Proc.new v. the ampersand sign?

break makes the block and the caller of the block return. In the following code:

proc = Proc.new { break }

The "caller" of the block which is converted to a Proc object is Proc.new. break is supposed to make the caller of the block return, but Proc.new has already returned.

In this code:

def iterator(&b); b.call; end
iterator { break }

The caller of the block is iterator, so it makes iterator return.

Why does Ruby include? behave differently when nested within an if/end conditional?

Here's what's happening in each of these examples:

First Example

This example outputs 1. Your string includes a j or J. regardless of the previous line. The my_string.include? check is being ignored as it's not used in a comparison anywhere, so the second line is just a regular puts.

Second Example

The second example is a little more interesting. ("j" or "J") is syntax in Ruby which will output the first of the provided arguments which evaluates to true. "j" evaluates to true because it's not nil or false, so it becomes the argument of the second include? method. include? is case-sensitive, so it will return false – the string Jack does not include a lowercase j.

You can try this out by running irb and entering something like 1 or 2 or false and 1; you'll see pretty quickly that the first true argument is returned (or false if no arguments are true).

There's no good way to make this work as-is, other than updating the include? check to use something like set intersections. An easier solution may be to downcase the input before checking characters.

Avdi Grimm posted a good video on using and and or in Ruby.

Third Example

The third example is calling include? twice on the string, and returning true when it hits the second call, hence the if statement being evaluated.

Update

papirtiger's answer got me thinking, so I did a bit of digging with Ripper using the following script:

require 'ripper'
require 'pp'

expression = <<-FOO
if true
puts 'Hello'
end
FOO

pp Ripper.sexp(expression)

Here's the result:

[:program,
[[:if,
[:var_ref, [:@kw, "true", [1, 3]]],
[[:command,
[:@ident, "puts", [2, 2]],
[:args_add_block,
[[:string_literal,
[:string_content, [:@tstring_content, "Hello", [2, 8]]]]],
false]]],
nil]]]

After updating the expression to the following:

expression = <<-FOO
if
true
puts 'Hello'
end
FOO

This was the new output:

[:program,
[[:if,
[:var_ref, [:@kw, "true", [2, 2]]],
[[:command,
[:@ident, "puts", [3, 2]],
[:args_add_block,
[[:string_literal,
[:string_content, [:@tstring_content, "Hello", [3, 8]]]]],
false]]],
nil]]]

It looks as though Ruby does indeed ignore any whitespace and evaluate the next expression. I don't have enough expertise to dig much deeper, but after trying a few more examples (such as throwing a dozen newlines in after an if statement), I'm convinced.

How do I do early return in ruby to reduce nested if?

Use next instead of return to exit the inner block early:

class A 
def run
yield
end
end

a = A.new
a.run do
puts "ola"
next
puts "hello"
end

In order to use return, it should be place directly inside the method body.

But when you pass a block to a function, with EventMachine.run do and with Fiber.new{, the code inside the block is not directly inside the function, but is passed as a parameter to a different function. Inside the block you cannot call return, but you can exit early with next.

I am guessing that the reason the designers decided this is because a return inside the block would cause confusion whether the block would return, or the entire method.

Why does a variable that gets assigned inside a Proc not persist over repeated calls of the Proc?

string_to_alter gets persisted because it's outside of the Proc block and part of the closure. The Proc block itself gets instantiated at each invocation, however, hence the lack of persistence of any of the variables defined within the block.

How can two arguments be passed to a method with a one-argument signature?

The & operator turns the Proc into a block, so it becomes a one-argument method with a block (which is called with yield). If you had left off the & so that it passed the Proc directly, you would have gotten an error.

How do I return early from a rake task?

A Rake task is basically a block. A block, except lambdas, doesn't support return but you can skip to the next statement using next which in a rake task has the same effect of using return in a method.

task :foo do
puts "printed"
next
puts "never printed"
end

Or you can move the code in a method and use return in the method.

task :foo do
do_something
end

def do_something
puts "startd"
return
puts "end"
end

I prefer the second choice.

Converting uneven rows to columns with FasterCSV

I would insert nulls to fill the holes in your matrix, something such as:

a = [[1, 2, 3], [3, 4]]

# This would throw the error you're talking about
# a.transpose

# Largest row
size = a.max { |r1, r2| r1.size <=> r2.size }.size

# Enlarge matrix inserting nils as needed
a.each { |r| r[size - 1] ||= nil }

# So now a == [[1, 2, 3], [3, 4, nil]]
aa = a.transpose

# aa == [[1, 3], [2, 4], [3, nil]]

Blocks and yields in Ruby

Yes, it is a bit puzzling at first.

In Ruby, methods can receive a code block in order to perform arbitrary segments of code.

When a method expects a block, you can invoke it by calling the yield function.

Example:

Take Person, a class with a name attribute and a do_with_name method. When the method is invoked it will pass the name attribute to the block.

class Person 
def initialize( name )
@name = name
end

def do_with_name # expects a block
yield( @name ) # invoke the block and pass the `@name` attribute
end
end

Now you can invoke this method and pass an arbitrary code block.

person = Person.new("Oscar")

# Invoking the method passing a block to print the value
person.do_with_name do |value|
puts "Got: #{value}"
end

Would print:

Got: Oscar

Notice the block receives as a parameter a variable called value. When the code invokes yield it passes as argument the value of @name.

yield( @name )

The same method can be invoked with a different block.

For instance to reverse the name:

reversed_name = ""

# Invoke the method passing a different block
person.do_with_name do |value|
reversed_name = value.reverse
end

puts reversed_name

=> "racsO"

Other more interesting real life examples:

Filter elements in an array:

 days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]  

# Select those which start with 'T'
days.select do | item |
item.match /^T/
end

=> ["Tuesday", "Thursday"]

Or sort by name length:

 days.sort do |x,y|
x.size <=> y.size
end

=> ["Monday", "Friday", "Tuesday", "Thursday", "Wednesday"]

If the block is optional you can use:

yield(value) if block_given?

If is not optional, just invoke it.

You can try these examples on your computer with irb (Interactive Ruby Shell)

Here are all the examples in a copy/paste ready form:

class Person 
def initialize( name )
@name = name
end

def do_with_name # expects a block
yield( @name ) # invoke the block and pass the `@name` attribute
end
end

person = Person.new("Oscar")

# Invoking the method passing a block to print the value
person.do_with_name do |value|
puts "Got: #{value}"
end

reversed_name = ""

# Invoke the method passing a different block
person.do_with_name do |value|
reversed_name = value.reverse
end

puts reversed_name

# Filter elements in an array:
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]

# Select those which start with 'T'
days.select do | item |
item.match /^T/
end

# Sort by name length:
days.sort do |x,y|
x.size <=> y.size
end


Related Topics



Leave a reply



Submit