Ruby If VS End of the Line If Behave Differently

Ruby if vs end of the line if behave differently?

This is a very good question. It has to do with the scoping of variables in Ruby.

Here is a post by Matz on the Ruby bug tracker about this:

local variable scope determined up to down, left to right. So a local variable first assigned in the condition of if modifier is not effective in the left side if body. It's a spec.

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.

Ruby 2.2.4 lexing an assignment in a conditional

Both lines won't work. And both lines will work. It is schrödinger expression :).

You can run it twice in a new repl:

a =  b if b = "test"
#=> NameError: undefined local variable or method `b' for main:Object
a = b if b = "test"
#=> "test"

Let's look deeper, open a new repl:

defined(b)
#=> nil
a = b if b = "test"
#=> NameError: undefined local variable or method `b' for main:Object
defined(b)
#=> local-variable
b
#=> "test"
a = b if b = "test"
#=> "test"

So actually Ruby has evaluated b = "test" part and defined this variable in current scope. Both expressions a = b and if b = "test" were executed. More than it, if statement is executed before assignment statement:

c = p("assignment") && b if b = p("if") && "test"
#=> "if"
#=> "assignment"
#=> NameError: undefined local variable or method `b' for main:Object

But b variable was not defined in scope of assignment statement when it was evaluated first time. And on the second approach it was already defined so you received correct result.

So, Never do assignments in this way

Different behaviour of 'do .. end' and {..} block in ruby

sawa's answer is correct, but as the OP has asked for more clarification, I'm supplying my own answer.

All four of these method calls behave the same, passing a block to a foo method:

foo { ... }
foo do ... end
foo() { ... }
foo() do ... end

When you write two methods and a block, without parentheses around the arguments, it is unclear which method the block goes with:

foo bar { ... }
foo bar do ... end

The question is: "Am I passing a block to bar, and then passing its return value to foo? Or am I calling foo with bar as an argument and also passing along a block?"

With parentheses, you can make this clear using either block style:

# Passing a block to `bar`, and then passing the result to `foo`
foo( bar { ... } )
foo( bar do ... end )

# Passing an argument and block to `foo`
foo( bar ) { ... }
foo( bar ) do ... end

The difference between {…} and do…end that you have run into is where Ruby chooses to put the parentheses when you omit them. The two block notations have different precedence, and so you end with different results:

# Passing a block to `bar`, and then passing the result to `foo`
foo bar{ ... }
foo( bar do ... end )

# Passing an argument and block to `foo`
foo bar do ... end
foo( bar ){ ... }

So, specifically in your case:

# This code…
p a.sort do |x,y|
y <=> x
end

# …is the same as this code:
b = a.sort
p(b){ |x,y| y <=> x }

# Note that you are passing a block to the `p` method
# but it doesn't do anything with it. Thus, it is
# functionally equivalent to just:
p a.sort

And,

# This code…
p a.sort { |x,y|
y <=> x
}

# …is functionally the same as this code:
b = a.sort{ |x,y| y <=> x }
p b

Finally, if you still don't get it, perhaps deeply considering the following code and output will help:

def foo(a=nil)
yield :foo if block_given?
end

def bar
yield :bar if block_given?
:bar_result
end

foo bar { |m| puts "block sent to #{m}" }
#=> block sent to bar
#=> foo got :bar_result

foo( bar do |m| puts "block sent to #{m}" end )
#=> block sent to bar
#=> foo got :bar_result

foo( bar ){ |m| puts "block sent to #{m}" }
#=> foo got :bar_result
#=> block sent to foo

foo bar do |m| puts "block sent to #{m}" end
#=> foo got :bar_result
#=> block sent to foo

Notice that the first and last examples in the code immediately above differ only in whether they use {…} or do…end for the block.

Difference between inline if conditional and block if conditional in Ruby

I suspect it has something to with ruby parser. Because when writing inline if

inline_if = 'value will not set but no error' if inline_if

Ruby parser actually parse from left to right. So in above line it first encounters setter method of variable inline_if. so it actually defines this variable with value nil and then it checks for the condition if inline_if which will be evaluated as if nil.

Now with the block if condition

if block_if
block_if = 'forget about setting value, I will raise an error'
end

it first tries to access the block_if variable which is not yet defined. hence it will trow an error.

It's amazing thing that from programmer view both of above block should be evaluated equally. but they behave different

Why do Ruby instance method invocations behave differently when prefixed with 'self'?

Private methods can not be called with explicit receiver like self. This means that you can call a private method from within a class it is declared in as well as all subclasses of this class.

Here is a good article about that. It explains well why code like yours will raise NoMethodError.

On wikibooks there is also a good explanation of visibility levels in ruby.

For the reference you can bypass this with Object#send but generally it is considered as a bad practice to do so.

Assigning an undefined variable in a one line condition

Because the Ruby interpreter "creates" a local variable when it sees an assignment.

In the second case, it hasn't yet seen the assignment, so the variable doesn't exist when the expression is parsed.

To be more precise, a method is first parsed into an internal representation, and then, perhaps, the code will eventually be called and actually executed.

Variables are "created" in that parsing pass. It's really more a matter of declaration, it just means that the interpreter becomes aware of them. They won't be created in the sense of being given space or a value until the surrounding method is called by someone.

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.

What does ||= (or-equals) mean in Ruby?

This question has been discussed so often on the Ruby mailing-lists and Ruby blogs that there are now even threads on the Ruby mailing-list whose only purpose is to collect links to all the other threads on the Ruby mailing-list that discuss this issue.

Here's one: The definitive list of ||= (OR Equal) threads and pages

If you really want to know what is going on, take a look at Section 11.4.2.3 "Abbreviated assignments" of the Ruby Language Draft Specification.

As a first approximation,

a ||= b

is equivalent to

a || a = b

and not equivalent to

a = a || b

However, that is only a first approximation, especially if a is undefined. The semantics also differ depending on whether it is a simple variable assignment, a method assignment or an indexing assignment:

a    ||= b
a.c ||= b
a[c] ||= b

are all treated differently.



Related Topics



Leave a reply



Submit