Feature #13067
closedTrueClass,FalseClass to provide `===` to match truthy/falsy values.
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.
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)
-
This would break a lot of code. As an example, I found 13 instances of
when true/false
in Rails' code that would break. -
There is no good replacement for the current uses of
when true
andwhen false
. In particular,when true
would basically require separateif
.# 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
-
This makes
when true
almost meaningless and very misleading.when false
is not useful as it can already be written easily withwhen false, nil
-
Increases the confusion of
true
vs "truthy" andfalse
vs "falsey" -
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
- 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:
- There is no good replacement for the current uses of
when true
andwhen false
. In particular,when true
would basically require separateif
.# 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 makecase / 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) almost 5 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
?