C# ?? operator in Ruby?
Asked Answered
I

5

46

Is it possible to implement the ?? operator in Ruby?

a = nil
b = 1

x = a ?? b # x should == 1
x = b ?? 2 # x should == 1
Improvise answered 4/6, 2009 at 21:28 Comment(0)
H
33

You're looking for conditional assignment:

a ||= b  # Assign if a isn't already set

and the || operator

a = b || 2 # Assign if b is assigned, or assign 2
Hinder answered 4/6, 2009 at 21:34 Comment(3)
This won't work, for the reasons I outlined in my answer. Try setting a to false in the first example or b to false in the second example.Dantedanton
Correct, @JörgWMittag. Beware of the truthy/falsey!Steiermark
Also I'm pretty sure you can't get a "can't assign to nil" error with ??. But you could write a method ifNil on object and on NilClass to evaluate a block as in smalltalkKhichabia
G
62

In Ruby, the short-circuiting Boolean operators (||, &&, and and or) do not return true or false, but rather the first operand that determines the outcome of the entire expression. This works, because Ruby has a rather simple idea of truth. Or rather, it has a rather simple idea of falsehood: nil is false, and obviously false is false. Everything else is true.

So, since || is true when at least one of its operands is true, and operands are evaluated from left to right, this means that a || b returns a, when a is true. But when a is false, then the outcome of the expression is solely dependent on b, and thus b is returned.

That means that, because nil is false, you can just use || instead of ?? for the examples that you gave. (There is also the nifty a ||= b operator, which kind of works like a || a = b, but not quite.)

However, that only works, because you don't use Booleans in your examples. If you expect to deal with Boolean values, that won't work:

b = false

x = b || 2 # x should be == false, but will be 2

In that case, you will have to use #nil?, and a conditional expression:

b = false

x = unless b.nil? then b else 2 end # x should be == 2

or using the ternary conditional operator:

b = false

x = b.nil? ? 2 : b # x should be == false

If you want to, you can wrap that up in a nice method:

class Object
  def _? b = nil
    return self
  end
end

class NilClass
  def _? b = nil
    return yield if block_given?
    return b
  end
end

b = false

x = b._? { 2 } # x should be == false
x = b._? 2 # x should be == false

This cute snippet brought to you by polymorphism, open classes and the fact that nil is actually an object representing nothingness (unlike, say, Java, where null is actually nothing).

Gnni answered 4/6, 2009 at 22:8 Comment(3)
a ||= b isn't so much "kind of" a || a = b, but exactly a = a || b. It is not conditional assignment, the assignment will always happen.Snotty
@Theo: that's what the current draft of the ISO Ruby Language Specification says, but that's wrong. MRI, YARV, JRuby, Rubinius, XRuby, MacRuby, IronRuby, Ruby.NET, MagLev, SmallRuby, tinyrb, RubyGoLightly and every other Ruby implementation ever created, implement it as short-circuiting conditional assignment. The Ruby Programming Language (co-written by matz himself), Programming Ruby and every other Ruby book ever written document it that way. The RubySpec testsuite tests it that way. Several discussions on StackOverflow and dozens of discussions on ruby-talk say so. Matz himself says so.Dantedanton
The mandatory link to peter cooper's article: rubyinside.com/…Ingesta
H
33

You're looking for conditional assignment:

a ||= b  # Assign if a isn't already set

and the || operator

a = b || 2 # Assign if b is assigned, or assign 2
Hinder answered 4/6, 2009 at 21:34 Comment(3)
This won't work, for the reasons I outlined in my answer. Try setting a to false in the first example or b to false in the second example.Dantedanton
Correct, @JörgWMittag. Beware of the truthy/falsey!Steiermark
Also I'm pretty sure you can't get a "can't assign to nil" error with ??. But you could write a method ifNil on object and on NilClass to evaluate a block as in smalltalkKhichabia
C
2
x = b || 2

It (?? in C#) is called the coalesce operator.

Commissariat answered 4/6, 2009 at 21:36 Comment(1)
This won't work, for the reasons I outlined in my answer. Try setting b to false in the example.Dantedanton
P
1

There is the coalesce gem, which is as close as you'll get.

nil || 5 # => 5
false || 5 # => 5 :(
false._? 5 # => false :)
Pinnatiped answered 12/1, 2014 at 0:58 Comment(0)
K
-1

Ruby has a null coalescing operator &., called the "Safe Navigation Operator":

irb(main):004:0> a = nil
=> nil
irb(main):005:0> a&.foo
=> nil
irb(main):006:0> a = Struct.new(:foo).new(foo: 3)
=> #<struct  foo=3>
irb(main):007:0> a&.foo
=> 3

It's not exactly what you're asking for, but this question ranks so high on google for "ruby null coalescing operator" that it's worth mentioning here.

Kkt answered 19/3 at 17:37 Comment(2)
&. is not a null coalescing operator but the safe navigation operator; it does something else entirely. Thus, your answer is entirely unrelated to the question.Doubleripper
@HolgerJust fair, I use the terms interchangably but it's not correct. ¯\_(ツ)_/¯Kkt

© 2022 - 2024 — McMap. All rights reserved.