Project

General

Profile

Actions

Feature #13067

closed

TrueClass,FalseClass to provide `===` to match truthy/falsy values.

Added by matz (Yukihiro Matsumoto) almost 8 years ago. Updated over 4 years ago.

Status:
Closed
Target version:
-
[ruby-dev:49916]

Description

I propose to make TrueClass, FalseClass to provide === method to match truthy values (TrueClass), and falsy values (FalseClass), so that we can use true and false for case pattern matching. And we can pick truthy values using grep e.g. ary.grep(true).

Matz.


Related issues 1 (0 open1 closed)

Related to Ruby master - Feature #11286: [PATCH] Add case equality arity to Enumerable's sequence predicates.Closedmatz (Yukihiro Matsumoto)Actions
Actions #1

Updated by matz (Yukihiro Matsumoto) almost 8 years ago

  • Related to Feature #11286: [PATCH] Add case equality arity to Enumerable's sequence predicates. added

Updated by nobu (Nobuyoshi Nakada) almost 8 years ago

It affects case/when clause too, and breaks optparse and rubygems at least.

diff --git i/lib/optparse.rb w/lib/optparse.rb
index afeff80740..216dd38732 100644
--- i/lib/optparse.rb
+++ w/lib/optparse.rb
@@ -394,9 +394,9 @@
 #
 class OptionParser
   # :stopdoc:
-  NoArgument = [NO_ARGUMENT = :NONE, nil].freeze
-  RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true].freeze
-  OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false].freeze
+  NoArgument = [NO_ARGUMENT = :NONE, NilClass].freeze
+  RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, TrueClass].freeze
+  OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, FalseClass].freeze
   # :startdoc:
 
   #
diff --git i/object.c w/object.c
index 5f0055fb5a..c566bc57c5 100644
--- i/object.c
+++ w/object.c
@@ -1260,6 +1260,7 @@ true_to_s(VALUE obj)
 /*
  *  call-seq:
  *     true & obj    -> true or false
+ *     true === obj  -> true or false
  *
  *  And---Returns <code>false</code> if <i>obj</i> is
  *  <code>nil</code> or <code>false</code>, <code>true</code> otherwise.
@@ -1297,6 +1298,7 @@ true_or(VALUE obj, VALUE obj2)
 /*
  *  call-seq:
  *     true ^ obj   -> !obj
+ *     false === obj -> !obj
  *
  *  Exclusive Or---Returns <code>true</code> if <i>obj</i> is
  *  <code>nil</code> or <code>false</code>, <code>false</code>
@@ -3603,7 +3605,7 @@ InitVM_Object(void)
     rb_define_method(rb_cTrueClass, "&", true_and, 1);
     rb_define_method(rb_cTrueClass, "|", true_or, 1);
     rb_define_method(rb_cTrueClass, "^", true_xor, 1);
-    rb_define_method(rb_cTrueClass, "===", rb_equal, 1);
+    rb_define_method(rb_cTrueClass, "===", true_and, 1);
     rb_undef_alloc_func(rb_cTrueClass);
     rb_undef_method(CLASS_OF(rb_cTrueClass), "new");
     /*
@@ -3618,7 +3620,7 @@ InitVM_Object(void)
     rb_define_method(rb_cFalseClass, "&", false_and, 1);
     rb_define_method(rb_cFalseClass, "|", false_or, 1);
     rb_define_method(rb_cFalseClass, "^", false_xor, 1);
-    rb_define_method(rb_cFalseClass, "===", rb_equal, 1);
+    rb_define_method(rb_cFalseClass, "===", true_xor, 1);
     rb_undef_alloc_func(rb_cFalseClass);
     rb_undef_method(CLASS_OF(rb_cFalseClass), "new");
     /*

Updated by matz (Yukihiro Matsumoto) almost 8 years ago

Nobu, I think the benefit outpass the drawbacks.

Regarding your patch, I think === should be its own RDoc entries.

Matz.

Updated by marcandre (Marc-Andre Lafortune) almost 8 years ago

  • Assignee set to matz (Yukihiro Matsumoto)
  1. This would break a lot of code. As an example, I found 13 instances of when true/false in Rails' code that would break.

  2. There is no good replacement for the current uses of when true and when false. In particular, when true would basically require separate if.

    # From rails/actionpack/lib/action_dispatch/middleware/ssl.rb
    # Current:
    def normalize_hsts_options(options)
      case options
      when false
        # ...
      when nil, true
        # ...
      else
        # ...
      end
    end
    
    # Ugly rewrite necessary:
    def normalize_hsts_options(options)
      if options == true
        # ...
      else
        case options
        when nil
          # repeated code from `options == true` above...
        when false  # `nil` case must be already taken care of
          # ...
        else
          # ...
        end
    end
    
  3. This makes when true almost meaningless and very misleading. when false is not useful as it can already be written easily with when false, nil

  4. Increases the confusion of true vs "truthy" and false vs "falsey"

  5. More importantly, I can not think of a single valid usecase.

ary.grep(true) was mentioned. I very much doubt there's much need to do that. We often want to exclude nil from a list (that's why we have compact), but excluding both nil and false seem odd, like the values were not computed properly. If there was such a need: ary.select(&:itself) works fine and is clear and concise enough.

ary.grep(false) seems completely meaningless to me (what can be the use of an array of nil and false values?) but if it isn't, ary.reject(&:itself) works too.

In short, I find the proposal both completely useless and confusing. In case it is somehow deemed necessary, then please define TRUTHY and FALSEY instead of changing the definitions that have been valid for 20 years.

Updated by shevegen (Robert A. Heiler) almost 8 years ago

  1. Increases the confusion of true vs "truthy" and false vs "falsey"

I think that the words "true" and "false" are a lot easier to understand than "truthy" and "falsy".

There should be some document that states the valid values for these in ruby.

Off the top of my head, true being everything except for:

  • nil
  • false

Perhaps I missed something, but that seems to be like a very small list to remember for
what is false? I am confused about the confusion comment. I guess the part about code
that breaks is a valid concern though. There is a long way to go for ruby 3.x anyway. ;)

Updated by snood1205 (Eli Sadoff) almost 8 years ago

I think that a possible middle ground replacement would be to introduce truthy and falsy constants into TrueClass and FalseClass respectively. You could then do, for example,

case 1
when true then puts 'This will not match'
when TrueClass::TRUTHY puts 'But this will'
end

Updated by sawa (Tsuyoshi Sawada) almost 8 years ago

Marc-Andre Lafortune wrote:

  1. There is no good replacement for the current uses of when true and when false. In particular, when true would basically require separate if.
    # From rails/actionpack/lib/action_dispatch/middleware/ssl.rb
    # Current:
    def normalize_hsts_options(options)
      case options
      when false
        # ...
      when nil, true
        # ...
      else
        # ...
      end
    end

    # Ugly rewrite necessary:
    def normalize_hsts_options(options)
      if options == true
        # ...
      else
        case options
        when nil
          # repeated code from `options == true` above...
        when false  # `nil` case must be already taken care of
          # ...
        else
          # ...
        end
    end

I think there is an alternative.

case options
when FalseClass
  ...
when NilClass, TrueClass
  ...
...
end

which is actually my preferred way of writing.

In fact, I had often felt there is redundancy, or unnecessary freedom, between writing when true etc. and when TrueClass etc., and had to wonder which one to use (before I came to the conclusion to use the latter). If they would mean different things, as would be the case if this proposal is implemented, then that would be a desired move, I think.

Updated by tenderlovemaking (Aaron Patterson) almost 8 years ago

IMO the backwards incompatibility risks outweigh the rewards.

As Marc-Andre says, array.grep(true) and array.grep(false) could be replaced with array.select(&:itself) and array.reject(&:itself). Since case / when just sends ===, we can use a proc like this:

def foo val
  case val
  when true
    'true value'
  when 1
    'one'
  when :itself.to_proc
    'truthy'
  else
    'falsy'
  end
end

p foo(nil)         # falsy
p foo(false)       # falsy
p foo(true)        # true value
p foo(Object.new)  # truthy
p foo(1)           # one

The :itself.to_proc doesn't look so great as an alternative, but maybe we could make case / when support &:itself syntax.

Updated by marcandre (Marc-Andre Lafortune) almost 8 years ago

While :itself.to_proc works, I still can't see when one would need it, and it's much easier and clearer to check for nil and false

def foo val
  case val
  when true
    'true value'
  when 1
    'one'
  when nil, false
    'falsy'
  else
    'truthy'
  end
end

Updated by nobu (Nobuyoshi Nakada) almost 8 years ago

Aaron Patterson wrote:

The :itself.to_proc doesn't look so great as an alternative, but maybe we could make case / when support &:itself syntax.

case / when isn't the original concern, but just an unexpected side effect.
Everybody can write that foo method, and select(&method(:foo)) and so on, but it won't look nice.

Updated by matz (Yukihiro Matsumoto) almost 8 years ago

  • Status changed from Open to Closed

Compatibility issue was bigger than I expected. I withdraw this proposal.

Matz.

Updated by jonathanhefner (Jonathan Hefner) over 4 years ago

Although I like the minimalism and cleverness of :itself.to_proc, I don't think it reads as nicely. It also does not inspect nicely. For example,

raise "value does not match #{pattern.inspect}" unless pattern === value

Does not give a nice error message when pattern is :itself.to_proc.

Also, when you specifically want to match falsy values, :itself.to_proc must become :!.to_proc, which may not be obvious.

So, I think constants like TRUTHY and FALSEY would be helpful. Although they might not be appropriate as top-level constants. While trying to think of where they should be defined, I had the crazy idea of TrueClass#thy and FalseClass#y. In other words, true.thy == TRUTHY and false.y == FALSEY. That is too asymmetric and gimmicky, I think, but what about true.ish == TRUTHY and false.ish == FALSEY?

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0