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
Detect Similar Sounding Words in Ruby
How to Pass Content to Jekyll Default Converter After Custom Conversion
Unable to Delete File from Amazon S3 Using Ruby Script
Using Ruby and Mechanize to Fill in a Remote Login Form Mystery
Ruby Is Already Using the Class Name of My Model
Rails Cancan and State MAChine - Authorizing States
Insert Rows on Specific Line in a File
Project Euler #3 in Ruby Solution Times Out
Compass Error for Command 'Grunt Server'
Gets.Chomp Without Moving to a New Line
How to Use H2 as Embedded Database in Postgres-Compat Mode, from Jruby/Rails
Rails Generate Controller Gives Me Load Error
Selenium Webdriver Getting a Cookie Value
How to Call a Method That Is a Hash Value