use variable in if statement operator in ruby - ruby

I have a variable that contains either '&&' or '||' and I need to use it in an if statement like so.
operator = '&&'
result = 1===1 operator 2===2
#=> result = 1===1 && 2===2
How can I achieve this?
I have tried using #public_send but to no avail.
Any help would be appreciated.

The && operator is more of a low-level syntax element than & and | which are method calls. That makes it harder to do.
However, there's two ways:
a = true
b = false
c = true
If you want && equivalence:
[ a, b, c ].all?
# => false
[ a, c ].all?
# => true
If you want || equivalence:
[ a, b, c ].any?
# => true
[ a, b ].any?
# => true
[ b ].any?
# => false

You could do it like this:
operator = "&&"
result = eval "1===1 #{operator} 2===2"
does that do what you want?
or if 1===1 is just a placeholder here for some expression, assign the result to a variable, so:
operator = "&&"
a = 1===1
b = 2===2
result = eval "#{a} #{operator} #{b}"
(of course, as commenters have noted, you must pay attention to the security vulnerabilities of eval, e.g. check that the operator variable is either "&&" or "||".)
and if you really want an if statement:
if operator == '&&'
1===1 && 2===2
elsif operator == '||'
1===1 || 2===2
else
# dunno
end

You can use public_send, but even if it were allowed with &&, it would not make sense. Note that E1 && E2 shortcircuits, in that E2 is not evaluated if E1 is already falsy. How could this work with public_send?
If you want to have this effect, you have to give up shortcircuiting, which means that you have to use & and | instead of && and ||. If you insist that your variable operator contains something like '&&', and not, say :& (which would make more sense), you could do a
result = (1===1).public_send(operator[0], 2===2)
to use your example for demonstration.
UPDATE: The comment of tadman to my answer made me think that I should warn you about the following trap: Based on your question, I assumed that the arguments you want to connect with your operator are either true or false. In this case, my solution indeed should work. However, if you they can be just any truey or falsy value of some class X, you need to convert them to true or false, because otherwise, the operator may have a different interpretation (for instance, if you operands happen to be integer). Hence, for some general operand_e1_ and e2, which - as is the general rule in Ruby - are supposed to be interpreted als false if it is either false or nil, and is taken as true otherwise, the safe way to calculate your result would be
result = (!!e1).public_send(operator[0], !!e2)
where !! ensures that the expressions are converted to true and false so that the operators & and | can safely be applied.

Related

Logical OR order of operations/precedence not as expected

https://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Operators#Logical_Or
The binary "or" operator will return the logical disjunction of its
two operands. It is the same as "||" but with a lower precedence
As I understand it, as || has a higher precedence than "or", the code below will first test if 'b' is true, then 'c', and then if both are false will test 'a'. I would like to witness this but am not sure if my understanding is incorrect, or simply my test.
a = true
b = false
c = false
p a or b || c
==> true #but were b and c checked as expected?
My attempt to test this is as follows..
def atest
a = "string a"
a.include? 'string'
a
end
def btest
b = "string b"
b.include? 'string'
b
end
def ctest
c = "string c"
c.include? 'string'
c
end
puts "#{atest or btest || ctest}"
==> string a
I expected 'string b' to be returned... I'm completely new to programming since a week ago so I'm not sure, is my code wrong or is my understanding of the quote wrong?
edit: Appreciation on its own seems to be discouraged in responses, so I'll hide it here. Cheers for all the answers/further reading/code cleanup, it's clear now where I was going wrong.
In Ruby, &&, and, ||, and or are short-circuit operators:
[...] the second argument is executed or evaluated only if the first argument does not suffice to determine the value of the expression: when the first argument of the AND function evaluates to false, the overall value must be false; and when the first argument of the OR function evaluates to true, the overall value must be true.
In your example:
a or b || c
Ruby evaluates a, sees a truthy value and immediately returns it, without evaluating b || c.
or's lower precedence means that the expression is evaluated as:
a or (b || c)
instead of:
(a or b) || c
Just like
1 + 2 * 3
is evaluated as:
1 + (2 * 3)
because + has lower precedence than *.
But it doesn't change the order of evaluation. 1 is still evaluated before 2 * 3.
Also note that due to or's very low precedence
p a or b || c
is evaluated as:
(p a) or (b || c)
Neither b nor c will ever be printed this way.
Further readings:
Using “and” and “or” in Ruby
How to use Ruby’s English and/or operators without going nuts
Ruby: On The Perl Origins Of “&&” And “||” Versus “and” And “or”.
Sidenote: In your code there are lines that basically do nothing: a.include? 'string' is silently returning truthy, having no impact at all on the running code. These three functions might be rewritten as:
def atest
"string #{__callee__[0]}"
end
alias :btest :atest
alias :ctest :atest
You are confusing operator precedence with normal execution flow. Operator precedence is about what operation to execute in advance when we have two operations applied to the same operand. Otherwise, the execution order is left-to-right. E.g.
a + b * c
is executed as:
a
b
# oh! ambiguity: lookup the precedence table: will do multiplication
c # (otherwise a + b)
*
+

Why does this return `nil` or the array?

It seems that the following code randomly returns the expected array for d, or nil.
def f arg
arg
end
def g *args
args
end
x,y,z = [true, 'two', false]
if a = f(x) and b = f(y) and c = f(z) then
d = g(a,b,c)
end
p d
The = operator performs assignment. It's different from the equality operator (==), which you would use to compare two values.
Here's a typical example of how these are used:
def speak(name)
if name == 'Hal'
puts "I can't do that, Dave."
end
end
myName = 'Hal'
speak(myName)
Then the output is:
I can't do that, Dave.
You're using = in the condition of your if statement, where you probably meant to use ==. In irb this generates a warning like this:
(irb):9: warning: found = in conditional, should be ==
So you're probably wondering, If I used the wrong operator, why does my code work at all?
Your code can still execute because
Assignments are expressions, meaning they return values.
All values can be evaluated as "true" or "false" (truthy or falsey).
What this means is that, in your code, a = f(x), b = f(y), and c = f(z) are each returning a value in addition to assigning a variable.
In each case, the value returned is the same as what is assigned: true, "two", and false, respectively. Therefore, your if statement is evaluating this condition:
true and "two" and false
In Ruby, all values are "truthy" except false and nil.
The string "two" is truthy, because it is neither false nor nil. However, the false is of course, "falsey," meaning that the whole expression evaluates as false, and the body of the if statement does not execute, so that g is never called, and the value of d remains unchanged.

3 Equals or Case Equality operator

In Ruby Integer === 5 returns true. Similarly String === "karthik" returns true.
However, 5 === Integer returns false. And "karthik" === String.
Why is the operator not commutative?
The simple answer is: because it doesn't make sense. The relationship the operator describes is not commutative, why should the operator be?
Just look at your own examples: 5 is an Integer. But is Integer a 5? What does that even mean?
=== is the case subsumption operator, and subsumption doesn't commute.
The fact that the case subsumption operator uses equals signs, and that it is usually called the triple equals, threequals or case equality operator is terribly unfortunate since it not only has absolutely nothing to do with equality, but it also does not conform to many of the laws that equality conforms to, such as transitivity and as you mentioned commutativity.
For more of my ranting about === see
What does the === operator do in Ruby?
=== vs. == in Ruby
How does Integer === 3 work?
One very simple reason is that the is_a? relationship for classes just can't be commutative. Consider the case where both operands are classes:
Class === String
This will return true because String.is_a?(Class). However String === Class will return false, because Class.is_a?(String) is false and that is of course as it should be.
Another reason is that the semantics of === depends on its left operand. This has again two reasons: a) In ruby the semantics always depend on the left operand, because the left operand is the receiver of the method call and b) it is useful, so you can use e.g. classes, ranges and regexen in a case statement with the intended semantics.
Many operators are not commutative.
The === is called the "case equality operator" because it is called when branching is a case.
It is nice and useful that:
foo = 42
case foo
when Integer
# branches here
when String
# etc...
end
It would not be very useful if
foo = Integer
case foo
when 42
# would branch here??
when 666
# etc...
end
Note that in Ruby 1.9, the === operator on a Proc/lambda will call that Proc:
divisible_by_three = ->(x){x % 3 == 0}
divisible_by_three === 42 # => true
Again, very useful in a case statement, but not much in the reverse order.
it needs to implement case-when comparisons
It's normal to have non-commutative operators.
/ - % [] . -> ^ << >> < <= > >= && || = += -= ,
And as it happens, === exists in part as the case-when operator. That's rather elaborate in Ruby, and it couldn't be so if it had to be simplified to a commutative op.

Ruby ternary operator without else

Is there a ruby idiom for "If do-this," and "do-this" just as a simple command?
for example, I'm currently doing
object.method ? a.action : nil
to leave the else clause empty, but I feel like there's probably a more idiomatic way of doing this that doesn't involve having to specify a nil at the end. (and alternatively, I feel like taking up multiple lines of code would be wasteful in this case.
As a general rule: you pretty much never need the ternary operator in Ruby. The reason why you need it in C, is because in C if is a statement, so if you want to return a value you have to use the ternary operator, which is an expression.
In Ruby, everything is an expression, there are no statements, which makes the ternary operator pretty much superfluous. You can always replace
cond ? then_branch : else_branch
with
if cond then then_branch else else_branch end
So, in your example:
object.method ? a.action : nil
is equivalent to
if object.method then a.action end
which as #Greg Campbell points out is in turn equivalent to the trailing if modifier form
a.action if object.method
Also, since the boolean operators in Ruby not just return true or false, but the value of the last evaluated expression, you can use them for control flow. This is an idiom imported from Perl, and would look like this:
object.method and a.action
a.action if object.method?
Greg's answer is the best, but for the record, and even more than in C, expressions and statements are equivalent in Ruby, so besides a.action if o.m? you can also do things like:
object.method? && a.action
You can write (a; b; c) if d or even
(a
b
c
) if d
or for that matter: (x; y; z) ? (a; b c) : (d; e; f)
There is no situation in Ruby where only a single statement or expression is allowed...
result = (<expression> && <true value>) || <false value>
value = 1
result = (value == 1 && 'one' ) || 'two'
result #=> 'one'
Explain: value == 1 && 'one' #=> returns last expression result, value is equals 1 so and section will be evaluated, and return 'one'.
value = 0
result = (value == 1 && 'one' ) || 'two'
result #=> 'two'
Explain: value != 1 and 'and' expression will not be evaluated, but instad will be used 'or' expression and it returns 'two'
Another way this can be done on the same line is:
if object.method; a.action end
This is considered bad style by Rubocop because it uses a semicolon to terminate the expression, but I find it more readable in some conditions than tacking on the if statement at the end. It is easier to overlook an if statement at the end and I don't always want to return something if the condition isn't true(as you are forced into with a ternary operator).
You can also be a bit more verbose and rubocop friendly:
if object.method then a.action end

Keyword for exclusive or in ruby?

Does Ruby have a plain-English keyword for exclusive or, like they have "and" and "or"? If not, is this because exclusive or doesn't allow evaluation short-cutting?
No it doesn't, you can only use ^.
Don't know why there isn't particularly, may just be because it isn't as commonly used.
I ran into an issue because the '^' operator acts bitwise on numbers,
true ^ 1
=> false
1 ^ true
TypeError: can't convert true into Integer
true ^ 1
so my workaround was:
( !!a ^ !!b ) where the double-bang coerces them into booleans.
!!1 ^ !!true
=> false
!!1 ^ !!false
=> true
Firstly, I don't think shortcircuiting can sensibly apply to XOR: whatever the value of the first operand, the second needs to be examined.
Secondly, and, &&, or and || use shortcircuiting in all cases; the only difference between the "word" and "symbol" versions is precedence. I believe that and and or are present to provide the same function as perl has in lines like
process_without_error or die
I think the reason for not having a xor named function is probably that there's no point in a low-precedence operator in this case and that it's already a confusing enough situation!
Try ^
true ^ false #=> true
true ^ true #=> false
false ^ false #=> false
No plain english equivalent operator though.
As an alternative to Matt Van Horn's double negation trick for using XOR on arbitrary types, you can chain another XOR test, starting with nil. i.e.:
!!foo ^ !!bar
is equivalent to
nil ^ foo ^ bar
This looks neater to my eye, and I suppose requires one less logical operation
Any implementation of xor won't allow short circuiting. Both expressions need to be evaluated no matter what.
Ruby does provide the ^ operator, but this will choke on truthy values. I've implemented a function to handle the cases where I want an xor that behaves more like and and or:
def xor(a,b)
(a and (not b)) or ((not a) and b)
end
Unlike ^, this function can be used in situations similar to the following:
xor("hello".match(/llo/), false) # => true
xor(nil, 1239) # => true
xor("cupcake", false) # => false
John's answer appears incorrect. In irb with 1.9.3, xor("cupcake", false) returns true, as you'd expect.
1.9.3-p429 :104 > def xor(a,b)
1.9.3-p429 :105?> (a and (not b)) or ((not a) and b)
1.9.3-p429 :106?> end
=> nil
1.9.3-p429 :107 > xor(false, true)
=> true
1.9.3-p429 :108 > xor("cupcake", false)
=> true

Resources