how to execute multiple statements within a ruby code block - ruby

I have the following simplified code example:
class MyTimerClass
def timed_execution(&block)
...
end
def elapsed_time
...
return ...
end
end
...
t = MyTimerClass.new
t.timed_execution {1000.times {"foo".equal? "foo"}}
puts "block took #{t.elapsed_time} seconds to run."
What I want to do is print out "executed" on every execution of "foo".equal? "foo". Instead of putting this inside the timed_execution method I want to add it to the passed code block. something like...
t.timed_execution {1000.times {"foo".equal? "foo" puts "executed"}}
which is wrong. I guess what I really want to know is how to have multiple statements within a code block. Really simple question I know...

You can use do .. end instead of braces. Or you can even separate the statements with a semicolon.
t.timed_execution do
1000.times do
"foo".equal? "foo"
puts "executed"
end
end
OR
t.timed_execution { 1000.times { "foo".equal? "foo" ; puts "executed" } }

From the question, it seems that you had the idea that a code block in Ruby should be written on a single line. In fact, code blocks in Ruby can span over multiple lines.
Code blocks in Ruby are created by writing statements inside an opening '{' and an ending '}' or an opening 'do' and an ending 'end'. Arguments to the block go on the same line as the block opening. Example :
# Using 'do' and 'end'.
5.times do |i|
puts i
end
# Using '{' and '}'.
5.times { |i|
puts i
}
The above way of writing code blocks are preferred if you are going to have more than one statement inside the block. If you are sure you are only gonna need a single statement then you can also opt to follow one line block style (many people do I think, I myself personally go for this one for a single statement). Example :
# Using a single line.
5.times { |i| puts i }
It's allowed to put multiple statements inside a single line block with the help of semi-colons to separate statements, also it's okay to use multi-line block for a single statement. But its a good idea to not do the former one for better readability.
Also from my opinion it's better to use '{' and '}' for single line blocks and 'do' and 'end' for multi-line blocks.

Related

Why can't I use curly braces with a `for` loop in Ruby?

The Ruby Documentation says that "do/end is equivalent to curly braces", so why is it that when I attempt to do the following I do not receive any output:
a = [1, 2, 3]
for i in a {
puts i
}
When I perform the above I receive no output (but I don't receive an error message either). However, when I do the following everything is as it should be:
a = [1, 2, 3]
for i in a do
puts i
end
#=> 1
#=> 2
#=> 3
I know this can be done more idiomatically with the each statement, but that's not what I'm asking. What am I not understanding here?
The Ruby Documentation says that "do/end is equivalent to curly braces"
No, it doesn't. It says (bold emphasis mine):
In this context, do/end is equivalent to curly braces […]
What "in this context" means is defined directly before the half-sentence you quoted:
do
Paired with end, can delimit a code block
So, "in this context" here refers to the context of a block.
so why is it that when I attempt to do the following I do not receive any output
Because this is a completely different context, again quoting from the documentation you linked to:
do can also (optionally) appear at the end of a for/in statement. (See for for an example.)
The "also" in that sentence makes it very clear that this is a different usage of the keyword do that has nothing to do with the usage discussed in this section. And if you look at the documentation of for, you can see that there is no mention of curly braces being allowed.
When I perform the above I receive no output (but I don't receive an error message either).
That is not true. Your code is syntactically invalid because it is missing the end keyword to end the for/in expression, therefore you get a "syntax error, unexpected end-of-input" on line 4:
ruby -c -e 'a = [1, 2, 3]
for i in a {
puts i
}'
# -e:4: syntax error, unexpected end-of-input
And if you add the missing end, you get a in `<main>': undefined method `a' for main:Object (NoMethodError) on line 2:
ruby -e 'a = [1, 2, 3]
for i in a {
puts i
}
end'
# -e:2:in `<main>': undefined method `a' for main:Object (NoMethodError)
Again, this is expected because curly braces delimit a code block, so
a {
puts i
}
is interpreted as a code block being passed to a and since variables cannot receive arguments, only methods can, a must be a method. Therefore, Ruby rightfully complains about not finding a method named a.
There are three ways of delimiting the iterator expression from the loop body expression in a for/in loop (and the same applies to while and until loops, actually):
An expression separator. An expression separator can either be
a semicolon ;
a newline
The keyword do
So, the following would all be valid fixes for your code:
# non-idiomatic
for i in a; puts i end
# non-idiomatic
for i in a
puts i end
# the same but with idiomatic indentation and whitespace
for i in a
puts i
end
# idiomatic
for i in a do puts i end
# redundant, non-idiomatic
for i in a do
puts i
end
Note, that when I say "idiomatic" above, that is to be interpreted relative, since actually for/in loops as a whole are completely non-idiomatic, and you would rather do this:
a.each do |i|
puts i
end
or maybe
a.each(&method(:puts))
It is in general preferred to not mix I/O and data transformation, so another idiomatic solution would be to transform the data to the desired output first, then output it, like this:
puts a.join("\n")
Except that Kernel#puts will already treat Array arguments special and print each element on its own line (as documented at IO#puts), so the real correct idiomatic solution for your code would be just:
puts a
Take a look to the documentation here: For loop
It states:
Like while and until, the do is optional. The for loop is similar to
using each, but does not create a new variable scope.
And also
The for loop is rarely used in modern ruby programs.
So, be less Pythonic :) using Enumerator#each instead:
a.each { |a| puts a }

Repeating a block of code until the block itself returns false?

I want to:
pass a block to a method call, and then
pass that entire method call as the condition of a while loop,
even though I don't need to put any logic inside the loop itself.
Specifically, I have an array that I'd like to #reject! certain elements from based on rather complicated logic. Subsequent calls to #reject! may remove elements that were not removed on a previous pass. When #reject! finally stops finding elements to reject, it will return nil. At this point, I would like the loop to stop and the program to proceed.
I thought I could do the following:
while array.reject! do |element|
...
end
end
I haven't actually tried it yet, but this construction throws vim's ruby syntax highlighter for a loop (i.e., it thinks the first do is for the while statement, and thinks the second end is actually the end of the encapsulating method). I also tried rewriting this as an inline while modifier attached to a begin...end block,
begin; end while array.reject! do |element|
...
end
but it still screws up the highlighting in the same way. In any case, it feels like an abuse of the while loop.
The only way I could think of to accomplish this is by assigning the method call as a proc:
proc = Proc.new do
array.reject! do |element|
...
end
end
while proc.call do; end
which works but feels kludgy, especially with the trailing do; end.
Is there any elegant way to accomplish this??
It's not just vim, while array.reject! do |element| is invalid syntax:
$ ruby -c -e 'while array.reject! do |element| end'
-e:1: syntax error, unexpected '|'
while array.reject! do |element| end
^
You could use { ... } instead of do ... end:
while array.reject! { |element|
# ...
}
end
or loop and break:
loop do
break unless array.reject! do |element|
# ...
end
end
a little more explicit:
loop do
r = array.reject! do |element|
# ...
end
break unless r
end
Ruby lets you move your condition to the end of the loop statement. This makes it easy to store a result inside of the loop and check it against the conditional:
begin
any_rejected = arr.reject! { … }
end while any_rejected
This would work the same as doing end while arr.reject! { … }, but it's much clearer here what's happening, especially with a complicated reject!.
You're right that the Ruby parser thinks that do belongs to while, and doesn't understand where the second end is coming from. It's a precedence problem.
This code is just to show that it can be done. For how it should be done, see Stefan's answer :
array = (1..1000).to_a
while (array.reject! do |element|
rand < 0.5
end)
p array.size
end
It outputs :
473
238
113
47
30
18
8
1
0
My personal preference in situations where I need to call a method until the return value is what I want is:
:keep_going while my_method
Or more tersely I sometimes use:
:go while my_method
It's one line, and you can use the contents of the symbol to help document what's going on. With your block, I'd personally create a proc/lambda out of it and pass that to reject for clarity.
# Harder to follow, IMHO
:keep_going while array.reject! do |...|
more_code
end
# Easier to follow, IMHO
simplify = ->(...){ ... }
:keep_simplifying while array.reject!(&simplify)

How to stop outer block from inner block

I try to implement search function which looks for occurrence for particular keyword, but if --max options is provided it will print only some particular number of lines.
def search_in_file(path_to_file, keyword)
seen = false
File::open(path_to_file) do |f|
f.each_with_index do |line, i|
if line.include? keyword
# print path to file before only if there occurence of keyword in a file
unless seen
puts path_to_file.to_s.blue
seen = true
end
# print colored line
puts "#{i+1}:".bold.gray + "#{line}".sub(keyword, keyword.bg_red)
break if i == #opt[:max] # PROBLEM WITH THIS!!!
end
end
end
puts "" if seen
end
I try to use break statement, but when it's within if ... end block I can't break out from outer each_with_index block.
If I move break outside if ... end it works, but it's not what I want.
How I can deal with this?
Thanks in advance.
I'm not sure how to implement it in your code as I'm still learning Ruby, but you can try catch and throw to solve this.
def search_in_file(path_to_file, keyword)
seen = false
catch :limit_reached do
#put your code to look in file here...
throw :limit_reached if i == #opt[:max] #this will break and take you to the end of catch block
Something like this already exist here

How to use blocks properly?

I'm very new to Ruby, so please bear with me.
Why is it a syntax error to have "test" {|s| print s}? How about "test" do |s| print s end?
Thanks
You can't say this:
"test" { |s| print s }
because "test" is a string literal, not a method. The same would apply to your do/end version. You could say:
["test"].each { |s| print s }
though because Arrays are Enumerable and Enumerable has an each method.
The {} are usually used for one liners.
do/end for multiple lines.
But there is no rule, do what you prefer.
Notice:
If ever you need to pass several instructions in a one liner, separate them with ;
A block is just a chunk of code enclosed in braces or keywords do/end. As mentioned already, you typically use braces for one liners, and do/end for multiple lines of code. Blocks can appear only immediately after the calling of some method. You can think of a block as an anonymous method (one that doesn't have a method name).
In your code, you were placing a block immediately after a string literal, not a method invocation. Blocks can be used for looping, as such:
2.times { puts "hello" } # => 2
# >> hello
# >> hello
In the above code, times is a method that belongs to all integers (that is to say, it is a instance method of the Integer class). The times method executes the code in the block twice, and returns the object (2 in this case) you called it on. You can pass a block to any method, although methods that are not expecting them will simply ignore the block.
Blocks can take parameters. The parameters are placed between pipes (the '|' character). It turns out, the first example could have accepted a parameter as seen here:
2.times { |i| puts i.to_s + " hello" } # => 2
# >> 0 hello
# >> 1 hello
I've only just scratched the surface of the power of blocks. You can read more about blocks for free in the online version of Programming Ruby: The Pragmatic Programmer's Guide (aka PickAx Book). It is a couple editions old now, but for an introduction to Ruby, you should find it sufficient. Once you understand blocks, you can start using power features of Enumerable which is included in Arrays and Hashes.

Does begin . . . end while denote a 'block'?

temp = 98.3
begin
print "Your temperature is " + temp.to_s + " Fahrenheit. "
puts "I think you're okay."
temp += 0.1
end while temp < 98.6
In the above example, is everything between begin and end a block?
I'm still confused what a block is.
If you can't call it a block, what would you call that chunk of code between begin and end? Is it ok to call it a chunk?
Block has a special meaning in Ruby. According to Matz, Ruby's creator, you can look at a block as a nameless function - typically something that can be yielded into, and which may also take parameters.
You may see the following kind of disamiguation when describing Ruby syntax:
begin...end (what is called block in other languages) may sometimes be referred to simply as what it is, i.e. an expression (which may in turn contain other expressions - an expression is simply something that has a return value) in Ruby. Some references will still call it a begin/end block, or a code block, adding somewhat to the confusion
do...end or {...} will always be referred to as a block in Ruby
For examples, peruse the the Ruby syntax man page, e.g.
begin expression end
expression while expression
loop block
For further reading, see:
Programming Ruby
Ruby (from other languages)
Much, much more documentation
begin/end are strictly control flow, not blocks.
begin
puts "hi"
end
# => "hi"
The code runs immediately. If it was a block, it would have to been called somehow in order for the code in it to run, as in this example:
def a_method; end
a_method { puts "hi" }
# nothing..
def a_method
yield
end
a_method { puts "Hi!" }
# => "Hi!"

Resources