20

Ruby has a universal idea of "truthiness" and "falsiness".

Ruby does have two specific classes for Boolean objects, TrueClass and FalseClass, with singleton instances denoted by the special variables true and false, respectively.

However, truthiness and falsiness are not limited to instances of those two classes, the concept is universal and applies to every single object in Ruby. Every object is either truthy or falsy. The rules are very simple. In particular, only two objects are falsy:

Every single other object is truthy. This includes even objects that are considered falsy in other programming languages, such as

These rules are built into the language and are not user-definable. There is no to_bool implicit conversion or anything similar.

Here is a quote from the ISO Ruby Language Specification:

6.6 Boolean values

An object is classified into either a trueish object or a falseish object.

Only false and nil are falseish objects. false is the only instance of the class FalseClass (see 15.2.6), to which a false-expression evaluates (see 11.5.4.8.3). nil is the only instance of the class NilClass (see 15.2.4), to which a nil-expression evaluates (see 11.5.4.8.2).

Objects other than false and nil are classified into trueish objects. true is the only instance of the class TrueClass (see 15.2.5), to which a true-expression evaluates (see 11.5.4.8.3).

The executable Ruby/Spec seems to agree:

it "considers a non-nil and non-boolean object in expression result as true" do
  if mock('x')
    123
  else
    456
  end.should == 123
end

According to those two sources, I would assume that Regexps are also truthy, but according to my tests, they aren't:

if // then 'Regexps are truthy' else 'Regexps are falsy' end
#=> 'Regexps are falsy'

I tested this on YARV 2.7.0-preview1, TruffleRuby 19.2.0.1, and JRuby 9.2.8.0. All three implementations agree with each other and disagree with the ISO Ruby Language Specification and my interpretation of the Ruby/Spec.

More precisely, Regexp objects that are the result of evaluating Regexp literals are falsy, whereas Regexp objects that are the result of some other expression are truthy:

r = //
if r then 'Regexps are truthy' else 'Regexps are falsy' end
#=> 'Regexps are truthy'

Is this a bug, or desired behavior?

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Interesting thing is that `Regex.new("a")` is truthy. – mrzasa Oct 11 '19 at 11:46
  • `!!//` is false but `!!/r/` is true. Strange indeed. – max Oct 11 '19 at 12:15
  • @max `!!/r/` produces `false` for me using (RVM) Ruby 2.4.1. – 3limin4t0r Oct 11 '19 at 12:20
  • Sorry my bad @3limin4t0r. You are right. I must have done something really stupid like leaving out an exclaimation mark. – max Oct 11 '19 at 12:21
  • 2
    A hypothesis, I think that `//` in `if // then` is interpreted as a test (a shortcut for `if //=~nil then`) (that is always falsy whatever the pattern) and not as an Regexp instance. – Casimir et Hippolyte Oct 11 '19 at 12:54
  • I think `if //` will act upon `$_` as is the case for cli usage.. for example: `seq 10 15 | ruby -ne 'print if /[35]/'` is same as `seq 10 15 | ruby -ne 'print if $_ =~ /[35]/'` – Sundeep Oct 11 '19 at 13:25
  • `ruby -le 'print "foo" if //'` doesn't print anything, whereas `ruby -le '$_ = ""; print "foo" if //'` will print `foo`... so, I think it depends upon what is the default value of `$_` – Sundeep Oct 11 '19 at 13:30
  • As mentioned above, `!!/r/ #=> false`, but the prefixed `!`s are actually syntactic sugar for `BasicObject#!`, so written as `/r/.!.!`, `true` is returned regardless as to what `$_` is set to and as one would expect for Ruby. – Travis Jun 25 '20 at 19:08

2 Answers2

10

This isn’t a bug. What is happening is Ruby is rewriting the code so that

if /foo/
  whatever
end

effectively becomes

if /foo/ =~ $_
  whatever
end

If you are running this code in a normal script (and not using the -e option) then you should see a warning:

warning: regex literal in condition

This is probably somewhat confusing most of the time, which is why the warning is given, but can be useful for one lines using the -e option. For example you can print all lines matching a given regexp from a file with

$ ruby -ne 'print if /foo/' filename

(The default argument for print is $_ as well.)

matt
  • 78,533
  • 8
  • 163
  • 197
  • See also `-n`, `-p`, `-a` and `-l` options, as well as the handful of Kernel methods that are only available when `-n` or `-p` are used (`chomp`, `chop`, `gsub` and `sub`). – matt Oct 11 '19 at 13:45
  • There is also [a second part of the parser](https://github.com/ruby/ruby/blob/v2_7_0_preview1/parse.y#L10874-L10879) where that warning is emitted. I don’t know what’s going on there though. – matt Oct 11 '19 at 13:56
  • I believe that "second part" is the one that actually applies to this question. `NODE_LIT` with type `T_REGEXP`. The one you posted in your answer is for a *dynamic `Regexp` literal*, i.e. a `Regexp` literal which uses interpolation, e.g. `/#{''}/`. – Jörg W Mittag Oct 11 '19 at 14:12
  • @JörgWMittag I think you’re right. Poking around in the compiler and the generated bytecode, it looks like in the case of the dynamic regexp the parse tree is rewritten to explicitly add `$_` as a node which the compiler handles as normal, whilst in the static case it is all dealt with by the compiler. Which is a shame for me because “hey, you can see where the parse tree is rewritten here” makes for a nice answer. – matt Oct 11 '19 at 15:17
6

This is the result of (as far as I can tell) an undocumented feature of the ruby language, which is best explained by this spec:

it "matches against $_ (last input) in a conditional if no explicit matchee provided" do
  -> {
    eval <<-EOR
    $_ = nil
    (true if /foo/).should_not == true
    $_ = "foo"
    (true if /foo/).should == true
    EOR
  }.should complain(/regex literal in condition/)
end

You can generally think of $_ as the "last string read by gets"

To make matters even more confusing, $_ (along with $-) is not a global variable; it has local scope.


When a ruby script starts, $_ == nil.

So, the code:

// ? 'Regexps are truthy' : 'Regexps are falsey'

Is being interpreted like:

(// =~ nil) ? 'Regexps are truthy' : 'Regexps are falsey'

...Which returns falsey.

On the other hand, for a non-literal regexp (e.g. r = // or Regexp.new('')), this special interpretation does not apply.

// is truthy; just like all other object in ruby besides nil and false.


Unless running a ruby script directly on the command line (i.e. with the -e flag), the ruby parser will display a warning against such usage:

warning: regex literal in condition

You could make use of this behaviour in a script, with something like:

puts "Do you want to play again?"
gets
# (user enters e.g. 'Yes' or 'No')
/y/i ? play_again : back_to_menu

...But it would be more normal to assign a local variable to the result of gets and perform the regex check against this value explicitly.

I'm not aware of any use case for performing this check with an empty regex, especially when defined as a literal value. The result you've highlighted would indeed catch most ruby developers off-guard.

Tom Lord
  • 27,404
  • 4
  • 50
  • 77
  • I only used the conditional as an example. `!// #=> true` has the same behavior and is not in a conditional. I couldn't find any boolean context (conditional or not), where it behaves as expected. – Jörg W Mittag Oct 11 '19 at 13:50
  • @JörgWMittag Do you mean e.g. `!// ? true : false` returns `true`? I think this is the same point again -- it's being interpreted like: `!(// =~ nil) ? true : false` – Tom Lord Oct 11 '19 at 13:55
  • If you manually set `$_ = 'hello world'` before running the above code, then you should get a different result -- because `// =~ 'hello world'`, but does not match `nil`. – Tom Lord Oct 11 '19 at 13:56
  • No, I mean `!//` *without the conditional* evaluates to `true`. The spec you cited is about a `Regexp` literal in a conditional, but in this example, there is no conditional, so this spec doesn't apply. – Jörg W Mittag Oct 11 '19 at 13:58
  • @JörgWMittag note that `//.!` evals to false, so the regex itself is indeed true – mrzasa Oct 11 '19 at 14:01
  • 2
    Ah.. Yeah, very surprising. The behaviour seems to be linked, though: `puts !//; $_ = ''; puts !//` -- I suppose because the parser expands it like a macro; it doesn't necessarily need to be inside a conditional? – Tom Lord Oct 11 '19 at 14:20
  • (I'm going to show this to everybody who claims Ruby is easy or simple. I really like Ruby, but simple, it definitely ain't.) – Jörg W Mittag Oct 11 '19 at 14:55