Why do method declarations evaluate to nil in ruby? - ruby

Defining a method doesn't seem to evaluate to a truthy value as can be checked by putting one inside an if condition:
if(def some_method; puts "random text"; end) then
puts "declaration evaluates to true"
else
puts "declaration evaluates to false"
end
# => declaration evaluates to false
Why/How does a method declaration evaluate to nil?

It actually evaluates to nil. This makes sense; why would a method creation return anything?
irb(main):001:0> def test; print 'test'; end
=> nil
However, it has to return something, so to return "nothing" would be to return nil.

Every statement in Ruby evaluates to something. The def statement's value is not supposed to be checked and is therefore nil.
You will find the behavior you are looking for in the reflective "meta-programming" method define_method.
class EmptyClass
m = define_method(:newmethod) {p "I am the new method"}
p m # => <Proc:0x50b3f359#E:\NetBeansProjects\RubyApplication1\lib\main.rb:6>
end

From Ruby gotchas:
Boolean evaluation of non-boolean data is strict: 0, "" and [] are all evaluated to true. In C, the expression 0 ? 1 : 0 evaluates to 0 (i.e. false). In Ruby, however, it yields 1, as all numbers evaluate to true; only nil and false evaluate to false. A corollary to this rule is that Ruby methods by convention — for example, regular-expression searches — return numbers, strings, lists, or other non-false values on success, but nil on failure. This convention is also used in Smalltalk, where only the special objects true and false can be used in a boolean expression.

Method definions such as def some_method; puts "random text"; end always return nil.
Now, that means the method is evaluated to nil. According to the Ruby Documentation:
Returns false if obj is nil or false; true otherwise.
Since your method return nil, if will evaluate it as false therefore execute the else statement.

Related

Why does an empty String returns false when comparing to boolean in ruby?

It might be a dumb question, but can someone please explain this:
if ""
true
else
false
end
=> true (OK)
!!("")
=> true (OK)
"" === true
=> false (OK)
"" == true
=> false (But why?)
Have a nice day!
The basic concept of this question is a deep misunderstanding of Ruby operators. Here's the short of it - there's no such thing as operators in Ruby! All this !, =, == and === that you throw around - these are not operators.
So what's going on?
Ruby is an object oriented language (a real one, not like that fake Java and JavaScript things) and all these fancy character sequences you think of as operators are actually method calls on the object in their left:
== method usually checks for equality - is the object on the right equal in content.
=== method is not used for "strict equality" or "identity" like it is often in other languages - some Ruby objects that implement it use it for "membership" tests, like in ranges ((1..3) === 2 ==> true) or regular expressions (/el/ === "hello" ==> true you can think of regular expressions as the group of all strings that would match), others implement it as equals.
So - how is the if working? Well, if and other "forced boolean contexts" check for "falsehood". In Ruby we recognize two false values - false and nil, everything else will run the truth branch of an if. This is actually the method ! that you also used in your second example. This method is implemented in BasicObject to return true for all objects, except if the object's type is the type of false - FalseClass or the type for nil - NilClass, in which case it returns true.
So your examples actually mean:
Check for the truthiness of "". Because that value's type is neither FalseClass or NilClass, it is always true.
For the value "", call the method ! - which will return false because the object is neither a FalseClass or a NilClass - then call ! on the result, which will return true because false is a FalseClass instance.
For the value "" call the method === with the argument true, but since it is an alias for == - call that instead (see 4).
For the value "" call the method == with the argument true. String's implementation of == will never return true for an argument that isn't a String type.
The only falsy values in Ruby are false and nil. Everything else is "truthy," but not necessarily equal to true. This includes empty objects like a String, Hash, or Array.
Pragmatically, it might help to think of == as "comparably equivalent to" rather than "equals." For example:
1 == 1.0 #=> true
This is true even though one is an Integer and one is a Float because they are comparably equivalent in value, even if they aren't the same object or of the same type.
In the same way, "" is truthy because it is not comparably equivalent to false or nil. However, it's also not the same object type as true, nor comparably equivalent to true. An empty String is simply "not falsy," which makes it truthy but not actually true.
Remember, only false and nil are falsy. Everything else, and I mean everything, is truthy even if it isn't strictly speaking true.
In Ruby, the only "falsey types" are FalseClass and NilClass, which have the instances false and nil respectively. All other values are considered "truthy". This is possibly different to what you'd expect coming from other C-like values, in which we do things like this pretty freely:
int x = get_value();
if (x) { /* implied x != 0 }
So, if you had something like this in Ruby:
puts 0 if 0 # => "0"
puts 1 if "" # => "1"
puts 2 if [] # => "2"
puts 3 if false # => nil
puts 4 if true # => "4"
puts 5 if nil # => "5"
So, if "" acts truthy, why isn't it equal to true? If that were how we defined ==, then this would also need to resolve to true then, since both values are truthy:
"1" == "2"
The difference here is that == is asking if two things are the same, which "" and true are most certainly not. Further, Ruby does not automatically convert types for you (like other languages like JavaScript do), so "" does not automatically get converted to a boolean during its comparison.

I need to write a regex in Ruby that returns true / false

I am trying to write a regex that takes a word and returns true for words starting with a vowel and returns false for words starting with a consonant. I have never written regex before, and I'm a little confused on how to write the expression. This is what I have so far:
def starts_with_a_vowel?(word)
if word.match(/\A+[aeiou]/) == true
return true
else
return false
end
end
Edit: So if word = "boat" , expression should return false. If word = "apple", expression should return true.
word.match? /\A[aeiou]/i is literally all you need. (ruby >= 2.4)
It matches the beginning of the string \A followed by a vowel [aeiou] in a case-insensitive manner i returning a bool word.match?
Before ruby 2.4 you have to use word.match and convert it to a bool, which is easiest done by logically negating it twice with !!
EDIT:
OK.. So.. I never tested the code I formerly wrote.. it was meant only as some suggestion how to use the match with regex, but it not worked at all.. (that code snippet is now attached to the end of my answer fyi)
this here should be the working one:
def starts_with_a_vowel?(word)
!!word.capitalize.match(/\A+[AEIOU]/)
end
..but how it was mentioned by Eric Duminil here below in comments, the method/function is
not needed
!!word.capitalize.match(/\A+[AEIOU]/) can be used directly..
it returns true or false
but there are surely other (maybe better) solutions too..
..and here is the NOT working code, which I formerly wrote:
def starts_with_a_vowel?(word)
return word.match(/\A+[aeiou]/).length > 0
end
..the match method returns nil when not match and because nil has no length method defined, it raises NoMethodError
Here are a couple of ways to do this.
#1
word = 'Ahoy'
!!(word[0] =~ /[aeiou]/i)
#=> true
word = 'hiya'
!!(word[0] =~ /[aeiou]/i)
#=> false
The regex reads, "match a vowel, case indifferently (/i)". !! converts a thruthy value to true and a falsy value (nil or false) to false:
!!0 = !(!0) = !(false) = true
!!nil = !(!nil) = !(true) = false
#2
word = 'Ahoy'
(word[0] =~ /[aeiou]/i) ? true : false
#=> true
word = 'hiya'
(word[0] =~ /[aeiou]/i) ? true : false
#=> false
You're doing a lot of extra work for no reason. First, you don't need to check for equality with true; just if *expression* does the trick.
But in this case you don't need if at all. A regex match already returns a value that can be interpreted as a Boolean. =~ returns the index of the match as an integer (which is "truthy"); String#match returns a MatchData object (which is also truthy). Everything in Ruby is truthy except false and nil, and nil is what both =~ and String#match return if there's no match. So all you have to do is turn the result of one of those into the corresponding Boolean value, which you can do with !!. For example:
def starts_with_a_vowel? word
!!(word =~ /^[aeiou]/)
end
That !! is read "not not", by the way. The ! operator by itself treats its argument as Boolean and returns its opposite as an actual Boolean; that is !(some truthy value) returns false (not nil), and !(some falsey value) returns true (not just some truthy value). So applying ! twice turns any truthy value into true and any falsey value (false or nil) into false.
Do you have to use a regular expression? Just asking cause Ruby already provides String#start_with?
vowels = %w(a e i o u)
"boat".start_with?(*vowels)
# => false
"apple".start_with?(*vowels)
#=> true
In Ruby, you almost never need anything to return true or false. For boolean logic and if/case/unless statements, truthy/falsey are good enough. Also, don't forget to use case-insensitive Regex (with //i). A is a vowel :
class String
def starts_with_a_vowel?
self =~ /\A[aeiou]/i
end
end
if "Airplane".starts_with_a_vowel?
puts "Indeed"
end
#=> "Indeed"
If for some reason you really need true/false :
class String
def starts_with_a_vowel?
!(self =~ /\A[aeiou]/i).nil?
end
end

what does `![var_name]` mean in Ruby?

I am dealing with someone else's code base, and I am trying to decipher the following line:
if !foo #<~ this line
...
end
I noticed that foo is supposed to return false, but with this bang! method it returns true. Does this mean that this ![var] business is similar to saying:
if foo != nil
Yes, the above code means to run the code if the condition (foo) is false. I won't say its similar to foo != nil, its more like !(foo) where foo can be nil or false.
You can write
if !foo #<~ this line
...
end
as
unless foo
...
end
But as per Ruby style guide, you should use unless, only if the code can be represented in a single line.
do_something unless foo
It means "if not foo", and it effectively returns the opposite of foo. So if foo = true, !foo returns false, and if foo = false, !foo returns true.
!foo
is syntactic sugar for
foo.!
i.e. sending the message ! to the object returned by evaluating the expression foo.
The method ! is defined in BasicObject, and it is documented just like any other method, even if rather tersely:
!obj → true or false
Boolean negate.
In this case the ! negates the value that follows it. In Ruby, only false and nil are considered falsy values, or in other words, values that will be considered false in a conditional statement like if. All other values (true, 1, "some-string", etc...) are considered truthy (values that will be considered true in a conditional).
So !false, is true. !nil is true. !true is false.

Implicit value of if/unless statement in ruby

I'm new to Ruby, and trying to understand why this works (it does seem to, at least according to "the Master"):
def array_of_fixnums?(array)
true unless array.find { |item| item.class != Fixnum }
end
My concern is where the "false-ness" is coming from when the array contains non-fixnum values. Am I right to assume there is no implicit "else false" in the unless statement? In that case I assume it must be coming from the nil value returned by Enumerable#find. Is that correct?
If so, that seems a bit shaky. Might it be better to return false explicitly, like this?
array.find { |item| item.class != Fixnum } ? false : true
Is there another, better way entirely? Thanks for helping me wrap my head around this, and for any "best practice" suggestions.
Your method is returning nil not because find returns nil, but because if your inline conditional does not pass, the method has no explicit return value. This would be more clear if it were not inline, consider:
def array_of_fixnums?(array)
unless array.find { |item| item.class != Fixnum }
return true
end
# otherwise don't explicitly return (implicit nil)
end
While relying on the falsiness of nil will often work, it is problematic in that it does not follow the principle of least surprise. A ? method should return true or false.
But your method has worse problems. It uses confusing logic (a double negative), and itself relies on the falsiness of nil and the truthiness of not nil, to function. Consider what happens if your method were passed [false]. Oops.
The better way would be something like:
array.all? {|n| n.is_a? Fixnum }
The reasoning is that this method does exactly what it says, plainly.
Returning a boolean explicitly, while not necessarily wrong, is superfluous and often considered bad practice. Rather consider the example, which says, in ruby speak, is every one of the values in this array a Fixnum?. The result of that expression is what the method is after; there's no reason to evaluate it then return true|false.
From the find method doc:
Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or returns nil otherwise.
Thus, all you need is:
def array_of_fixnums?(array)
array.find { |item| item.class != Fixnum }
end
If anything is found, that will be returned, otherwise nil will be returned, and the method will evaluate do false.
However, as the item return could be an false or nil (if any item in the list is false or nil) I would recommend that you use the .any? method instead.
def array_of_fixnums?(array)
array.any? { |item| item.class != Fixnum }
end
Let's look at why true unless condition works as it does.
I believe x unless condition was provided as an esthetic improvement to x if !condition, but the two are equivalent (and, in my opinion, it is an improvement). To get rid of double-negatives, I'll just consider x if condition.
One of Ruby's basic tenets is that every statement must return a value (namely, the last expression evaluated). Therefore, x if condition must be evaluated as if condition then x else y end. But what is y? If y evaluated as true or false, x if condition would be meaningless when x is a boolean expression. To see why, consider:
w = (x == 5 if z == 6)
If w were true, we could conclude that x = 5 and z = 6, but if w were false, we wouldn't know whether z = 6 and x != 5, or z != 6 and x = 5 or x != 5. Considering that any value of y other than nil is evaluated as true or false, the only reasonable value for y is nil. That way, if w == nil is be available. Of course, this is still a problem (in other situations) when x could possibly be nil.
I would welcome clarification and elaboration. Perhaps there is also a less convoluted way of making this argument.

what is the value of if/unless modifier?

I could not find anywhere a formal specification of the if (or unless) modifier:
123 if x > 0
what is the value of the above statement if x is not above zero? irb suggests nil, but is it documented anywhere?
(yes, this is perhaps a stupid question, sorry, could not find a spec).
An expression that is not evaluated is the same as not existing. And your condition should be part of some code block such as definition or the main environment, etc. A code block without a content is evaluated to nil.
class A; end
# => nil
def foo; end
foo
# => nil
()
# => nil
begin; end
# => nil
eval("")
# => nil
So the reason your example returns nil has nothing to do with condition itself. It is just due to there being no evaluation.
Yes, it is nil. What else could it return? The statement in the if clause is used for the if itself, and the then statement is not executed. There is nothing to return, so nil.
Relevant spec
it "returns nil if else-body is empty and expression is false" do
if false
123
else
end.should == nil
end

Resources