Is == a special method in Ruby? - ruby

I understand that x == y in Ruby interpreted as a.==(y). I tried to check if I can achieve the same with custom method, foo, like this:
class Object
def foo(n)
self == n
end
end
class A
attr_accessor :x
end
a = A.new
a.x = 4
puts a.x.==(4) # => true
puts a.x.foo(4) # => true
puts a.x == 4 # => true
puts a.x foo 4 # => in `x': wrong number of arguments (1 for 0) (ArgumentError)
Unfortunately, this doesn't work. What am I missing ? Is == a special method in Ruby ?

No, == is not a special method in Ruby. It's a method like any other. What you are seeing is simply a parsing issue:
a.x foo 4
is the same as
a.x(foo(4))
IOW, you are passing foo(4) as an argument to x, but x doesn't take any arguments.
There is, however, special operator syntax, which allows you to write
a == b
instead of
a.== b
for a limited list of operators:
==
!=
<
>
<=
>=
<=>
===
&
|
*
/
+
-
%
**
>>
<<
!==
=~
!~
Also, there is special syntax that allows you to write
!a
and
~a
instead of
a.!
and
a.~
As well as
+a
and
-a
instead of
a.+#
and
a.-#
Then, there is
a[b]
and
a[b] = c
instead of
a.[] b
and
a.[]= b, c
and last but not least
a.(b)
instead of
a.call b

Methods that are operators are treated specially in Ruby, at least syntax-wise. The language is not as flexible as, say, in Haskell where you can turn any function into an infix operator by enclosing its name in backticks: the list on infix operators are pre-determined.
One of the problems that would arise from custom infixes is the handling of operator precedence and associativity: for eaxmple, how would the parser handle a statement like:
a foo b == c # should this be (a foo b) == c or a foo (b == c)

Related

Why symbol is not equal to symbol

I did:
x = :foo, y = %q{foo}.to_sym, x.==(y)
# => [:foo, :foo, false]
I am wondering why :foo is not equal to :foo.
Parallel assignment works differently in Ruby, and if you want multiple statements on one line, you need to separate them with semicolons (not commas).
The statement you executed:
x = :foo, y = %q{foo}.to_sym, x == y
Is equivalent to:
x = [:foo, y = %q{foo}.to_sym, x == y]
N.B. x is not yet defined when the right-hand side of the expression is evaluated, so the last term is effectively comparing nil and :foo. The y assignment also happens while the right-hand side of the expression is evaluating, and the result gets included in the array literal being assigned to x.
Here's what you meant to do:
x, y = :foo, %q{foo}.to_sym; x == y # => true
Just an aside, x.==(y) works because :== is technically a method, but in idiomatic Ruby you just write x == y. The interpreter knows what you mean.
Nice answer, but there is a logical leap from "x is not yet defined" to "the last term is effectively comparing nil and :foo.", in particular, why an undefined variable is evaluated as nil is unexplained.
This has to do with how variable hoisting is implemented in Ruby. It's an obnoxious feature of JavaScript too.

Evaluation of multiple conditions

I have a quick question :
In ruby, If I write
def test
foo && a == b && c == "bar"
end
if foo is null or false, will it keep evaluating the rest of the expression ?
Does it change anything if I do this instead
def test
a == b && c == "bar" if foo
end
thanks a lot
Theory
&& is a lazy operator, just like ||.
It means that in a && b, if a is false or nil, Ruby won't bother to check b because a && b will be false/nil anyway.
This behaviour is actually desired, because it saves time and could avoid NoMethodErrors.
if a && method_which_requires_a_non_nil_parameter(a)
# ...
end
method_which_requires_a_non_nil_parameter wouldn't be called at all if a is nil.
or :
x = x || long_method_to_calculate_x
is often used for caching, more often written as :
#x ||= long_method_to_calculate_x
Answer
def test
foo && a == b && c == "bar"
end
The rest of the expression won't be evaluated if foo is nil or false :
a, b, c could even be undefined without raising a NameError.
def test
a == b & c == "bar" if foo
end
If foo is truthy, the results will be exactly the same.
If foo is nil or false, the 2 equalities won't be evaluated, just like with the first example. But :
If foo is nil, both test will return nil.
If foo is false, first example will return false, second example will return nil.
"If foo is null or false, will it keep evaluating the rest of the expression?"
No, it will not
This table should help you in such questions:
The following table is ordered according to descending precedence (highest precedence at the top)
N A M Operator(s) Description
- - - ----------- -----------
1 R Y ! ~ + boolean NOT, bitwise complement, unary plus
(unary plus may be redefined from Ruby 1.9 with +#)
2 R Y ** exponentiation
1 R Y - unary minus (redefine with -#)
2 L Y * / % multiplication, division, modulo (remainder)
2 L Y + - addition (or concatenation), subtraction
2 L Y << >> bitwise shift-left (or append), bitwise shift-right
2 L Y & bitwise AND
2 L Y | ^ bitwise OR, bitwise XOR (exclusive OR)
2 L Y < <= >= > ordering
2 N Y == === != =~ !~ <=> equality, pattern matching, comparison
(!= and !~ may not be redefined prior to Ruby 1.9)
2 L N && boolean AND
2 L N || boolean OR
2 N N .. ... range creation (inclusive and exclusive)
and boolean flip-flops
3 R N ? : ternary if-then-else (conditional)
2 L N rescue exception-handling modifier
2 R N = assignment
2 R N **= *= /= %= += -= assignment
2 R N <<= >>= assignment
2 R N &&= &= ||= |= ^= assignment
1 N N defined? test variable definition and type
1 R N not boolean NOT (low precedence)
2 L N and or boolean AND, boolean OR (low precedence)
2 N N if unless while until conditional and loop modifiers

How to write a conditional lambda in Ruby?

I'd like to write a conditional lambda in Ruby. The Python equivalent of what I'd like to write is:
even = (lambda x: x if x % 2 == 0 else 0)
My attempt to write this in Ruby looks like:
even = -> (x) {x if x % 2 == 0 else 0}
Unfortunately, that does not work. Is there any way to fix this?
You have several options here to express this. The long-form is this:
if (x % 2 == 0)
x
else
0
end
Note that a trailing if or unless cannot have a secondary condition like else. You need to use the full form.
If you want a short version, you use the ternary operator:
(x % 2 == 0) ? x : 0
What you could also do is this:
(x % 2 == 0) and x or 0
As others have observed there's a method called even? which does the modulo for you, so that can collapse this further:
x.even? ? x : 0
Ruby's ternary operator has this syntax
x % 2 == 0 ? x : 0
You don't even need a conditional.
p = ->(x) { x*(1-x%2) }
p.call(4) #=> 4
p.call(5) #=> 0
Note procs can be called in multiple ways:
p[4] #=> 4
p.(4) #=> 4
p.yield(4) #=> 4
p === 4 #=> 4
p(4) #=> 4
The last of these may be archaic.
Wrong syntax. You can write it like this:
even = -> (x) {if x % 2 == 0; x; else; 0; end}
However, for such one-liners, it is more common to use the ternary ?: operator, as other have suggested in this thread.
Your syntax is wrong:
The then-block comes after the condition, not before it.
The then-block is separated from the condition by the then keyword, a semicolon, or a newline.
The conditional expression is terminated with an end keyword.
So, any of the following is valid:
# idiomatic:
if x % 2 == 0 then x else 0 end
# idiomatic:
if x % 2 == 0
x
else
0
end
# non-idiomatic:
if x % 2 == 0; x else 0 end
# non-idiomatic:
if x % 2 == 0 then
x
else
0
end
# non-idiomatic:
if x % 2 == 0
x else 0 end
# … and many other non-idiomatic variations
Ruby also has a conditional operator:
x % 2 ? x : 0
But I personally find it ugly and unreadable. It also has different precedence than the conditional expression, which may be surprising to some. (Witness the myriad of variations of the same question about the conditional operator here on StackOverflow.) And it is completely unnecessary: in C, where if is a conditional statement, the conditional operator is required because it is an expression. In Ruby, everything is an expression anyway, there are no statements, so of course if is an expression, too.

How do I create arithmetic statements using variables?

I'm trying to create a method to check if three variables a, b and c are a pythagorean triplet. I set it up with a known triplet: 3, 4, 5. This program won't run though and I can't figure out why.
a = 3
b = 4
c = 5
def triplet?
if a**2 + b ** 2 == c ** 2
puts 'pythagorean triplet'
else puts 'not a pythagorean triplet'
end
end
triplet?
It returns the error message:
undefined local variable or method `a' for main:Object (NameError)
Any help will be much appreciated.
a, b, and c are local to the scope they're defined in, and thus aren't visible to separate scopes (such as other methods). See the doc on Object#def:
Starts a new local scope; local variables in existence when the def block is entered are not in scope in the block, and local variables created in the block do not survive beyond the block.
What you want to do is pass numbers as parameters:
def triplet?(a, b, c)
if a**2 + b ** 2 == c ** 2
puts 'pythagorean triplet'
else puts 'not a pythagorean triplet'
end
end
triplet?(3, 4, 5)
This will define those three variables in the scope of the triplet? method, then you populate their values by passing them when you invoke the method.
A small point of note, by convention, predicate methods (that is, methods ending in ?) in Ruby conventionally return a boolean. To write this method idiomatically, you might say:
def triplet?(a, b, c)
a**2 + b ** 2 == c ** 2
end
if triplet?(3, 4, 5)
puts 'pythagorean triplet'
else
puts 'not a pythagorean triplet'
end
That way, triplet? will always return a boolean true or false, then you can use it in your code to write English-y sentences.
Within the definition block, which is the scope for local variables, a is not defined, hence the error message.
a = 3
b = 4
c = 5
def triplet?(a, b, c)
if a**2 + b ** 2 == c ** 2
puts 'pythagorean triplet'
else
puts 'not a pythagorean triplet'
end
end
triplet?(a, b, c)
def creates a function. Inside the function block, you have a scope. a, b, and c are not in that scope. Tell the function to take parameters a, b, c and pass it the parameters.
There is no relation between the name you give the function parameters and the function parameters you pass.
The following will also work:
x = 3
y = 4
z = 5
def triplet?(a, b, c)
if a**2 + b ** 2 == c ** 2
puts 'pythagorean triplet'
else
puts 'not a pythagorean triplet'
end
end
triplet?(x, y, z)

A more elegant solution to Ruby Koans' triangle.rb

I have been working through Ruby Koans and made it to about_triangle_project.rb in which you are required to write the code for a method, triangle.
Code for these items are found here:
https://github.com/edgecase/ruby_koans/blob/master/koans/about_triangle_project.rb
https://github.com/edgecase/ruby_koans/blob/master/koans/triangle.rb
In triangle.rb, I created the following method:
def triangle(a, b, c)
if ((a == b) && (a == c) && (b == c))
return :equilateral
elsif ((a == b) || (a == c) || (b == c))
return :isosceles
else
return :scalene
end
end
I know from reading Chris Pine's "Learn to Program" there is always more than one way to do things. Although the above code works, I can't help but think there is a more elegant way of doing this. Would anyone out there be willing to offer their thoughts on how they might make such a method more efficient and compact?
Another thing I am curious about is why, for determining an equilateral triangle, I was unable to create the condition of (a == b == c). It is the proof for an equilateral triangle but Ruby hates the syntax. Is there an easy explanation as to why this is?
There is an easy explanation for why that is:
== in Ruby is an operator, which performs a specific function. Operators have rules for determining what order they're applied in — so, for example, a + 2 == 3 evaluates the addition before the equality check. But only one operator at a time is evaluated. It doesn't make sense to have two equality checks next to each other, because an equality check evaluates to true or false. Some languages allow this, but it still doesn't work right, because then you'd be evaluating true == c if a and b were equal, which is obviously not true even if a == b == c in mathematical terms.
As for a more elegant solution:
case [a,b,c].uniq.size
when 1 then :equilateral
when 2 then :isosceles
else :scalene
end
Or, even briefer (but less readable):
[:equilateral, :isosceles, :scalene].fetch([a,b,c].uniq.size - 1)
Another approach:
def triangle(a, b, c)
a, b, c = [a, b, c].sort
raise TriangleError if a <= 0 or a + b <= c
return :equilateral if a == c
return :isosceles if a == b or b == c
return :scalene
end
I borrowed Chuck's cool uniq.size technique and worked it into an oo solution. Originally I just wanted to extract the argument validation as a guard clause to maintain single responsibility principle, but since both methods were operating on the same data, I thought they belonged together in an object.
# for compatibility with the tests
def triangle(a, b, c)
t = Triangle.new(a, b, c)
return t.type
end
class Triangle
def initialize(a, b, c)
#sides = [a, b, c].sort
guard_against_invalid_lengths
end
def type
case #sides.uniq.size
when 1 then :equilateral
when 2 then :isosceles
else :scalene
end
end
private
def guard_against_invalid_lengths
if #sides.any? { |x| x <= 0 }
raise TriangleError, "Sides must be greater than 0"
end
if #sides[0] + #sides[1] <= #sides[2]
raise TriangleError, "Not valid triangle lengths"
end
end
end
def triangle(a, b, c)
if a == b && a == c # transitivity => only 2 checks are necessary
:equilateral
elsif a == b || a == c || b == c # == operator has the highest priority
:isosceles
else
:scalene # no need for return keyword
end
end
class TriangleError < StandardError
end
def triangle(a, b, c)
sides = [a,b,c].sort
raise TriangleError if sides.first <= 0 || sides[2] >= sides[1] + sides[0]
return :equilateral if sides.uniq.length == 1
return :isosceles if sides.uniq.length == 2
:scalene
end
Here is my solution:
def triangle(a, b, c)
sides = [a, b, c].sort
raise TriangleError, "Invalid side #{sides[0]}" unless sides[0] > 0
raise TriangleError, "Impossible triangle" if sides[0] + sides[1] <= sides[2]
return [:scalene, :isosceles, :equilateral][ 3 - sides.uniq.size ]
end
Hmm.. I didn't know about uniq - so coming from smalltalk (ages ago) I used:
require 'set'
def triangle(a, b, c)
case [a, b, c].to_set.count
when 1 then :equilateral
when 2 then :isosceles
else :scalene
end
end
Coming from the matlab world, I am used to array functions 'any' and 'all', and was happy enough to find them in Ruby. So:
def triangle(a, b, c)
eqs = [a==b, a==c, b==c]
eqs.all?? :equilateral : eqs.any?? :isosceles : :scalene
end
No idea whether that's optimal though, in terms of readability, computation time... (ruby noob).
Here is my solution:
def triangle(a, b, c)
return :equilateral if a == b and b == c
return :isosceles if ( a == b or b == c or a == c )
return :scalene
end

Resources