Feature #18369
openusers.detect(:name, "Dorian") as shorthand for users.detect { |user| user.name == "Dorian" }
Added by dorianmariefr (Dorian Marié) almost 3 years ago. Updated almost 3 years ago.
Description
Hi,
I was thinking I often do things like collection.detect { |item| item.attribute == value }
and a shorthand like collection.detect(:attribute, value)
would be quite useful
What do you think?
And I know there is collection.detect { _1.attribute == value }
but I try not to use _1
and this syntax would be shorter and simpler
Could also apply to other methods like all?
(collection.all?(:attribute, value)
), and basically any Enumerable method https://rubydoc.info/stdlib/core/Enumerable
Updated by dorianmariefr (Dorian Marié) almost 3 years ago
- Backport deleted (
2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN) - Tracker changed from Bug to Feature
Updated by dorianmariefr (Dorian Marié) almost 3 years ago
Could also be users.detect(&:name, "Dorian")
Updated by Dan0042 (Daniel DeLorme) almost 3 years ago
So you try not to use _1
... just out of curiosity, would you use this?
collection.detect{ .attribute == value }
Updated by dorianmariefr (Dorian Marié) almost 3 years ago
Dan0042 (Daniel DeLorme) wrote in #note-3:
So you try not to use
_1
... just out of curiosity, would you use this?
collection.detect{ .attribute == value }
I don't think so, still not explicit what is going on, and there is the overhead of new syntax
Updated by sawa (Tsuyoshi Sawada) almost 3 years ago
I think that is too specific to be a part of Ruby core. I don't think this feature would be accepted.
I think you can define a Proc constructor method for yourself like the following:
def attreql k, v
Proc.new{_1.send(k) == v}
end
Then, you can do:
class A
attr_reader :foo, :bar
def initialize foo, bar
@foo, @bar = foo, bar
end
end
collection = [A.new(1, 2), A.new(3, 4), A.new(5, 6)]
collection.detect(&attreql(:foo, 3)) # => #<A:0x00007fb751064630 @foo=3, @bar=4>
collection.all?(&attreql(:bar, 7)) # => false
The strength of doing it like this compared to your proposal is that it is more flexible. You can do:
def attrlt k, v
Proc.new{_1.send(k) < v}
end
collection.detect(&attrlt(:foo, 3)) # => #<A:0x00007fd3ab8a4680 @foo=1, @bar=2>
collection.all?(&attrlt(:bar, 7)) # => true
Updated by dorianmariefr (Dorian Marié) almost 3 years ago
Maybe the feature would be to be possible to have arguments after a block, e.g.
def detect(&block, value)
User.all.detect { |user| block.call(user) == value }
end
detect(&:first_name, "Dorian")
Updated by nobu (Nobuyoshi Nakada) almost 3 years ago
Since Enumerable#detect
or Enumerable#find
has the argument for the different purpose, I think that the extension in this way is not acceptable and should be a separate method.
The "arguments after a block" is one of rejected ideas before the numbered parameters.
Updated by cvss (Kirill Vechera) almost 3 years ago
It's a good occasion to use the composition of Proc/Method objects:
collection.detect(&:first_name.to_proc>>"Dorian".method(:==))
If we had a shorthand operator for Object#method (#12125), it would look nicer:
collection.detect(&:first_name.to_proc>>"Dorian".:==)
And if we make a shorthand Symbol#>>
for the composition of a Symbol and a Proc, it would look even wonderful:
class Symbol
def >> b
to_proc >> b
end
end
collection.detect(&:first_name>>"Dorian".:==)
When you are frequently using such constructions you can read it easily, but it is definitely more confusing comparing to the old plain variant:
collection.detect{_1.first_name == "Dorian"}
Updated by sawa (Tsuyoshi Sawada) almost 3 years ago
cvss (Kirill Vechera) wrote in #note-8:
It's a good occasion to use the composition of Proc/Method objects:
collection.detect(&:first_name.to_proc>>"Dorian".method(:==))
Your trick forces the use of Yoda conditions, which may be tricky and cryptic.
Updated by baweaver (Brandon Weaver) almost 3 years ago
Pattern Matching may make a very interesting tie-in here for a short-hand:
# Struct provides built-in pattern matching abilities
Person = Struct.new(:first_name, :last_name, :age)
jim = Person.new("Jim", "Smith", 30)
jill = Person.new("Jill", "Smith", 20)
sue = Person.new("Sue", "Smith", 40)
people = [jim, jill, sue]
# Currently works
people.select { _1 in { first_name: /^J/, age: 18.. } }
# Potential 1: bare keywords
people.select { _1 in first_name: /^J/, age: 18.. }
# Potential 2: `in` shorthand
people.select(&in first_name: /^J/, age: 18..)
Generally I think 1 is doable, 2 is stretching, though it would be nice to have syntax that allows to shorten one-line matchers for predicates where they would be commonly used.
Updated by zverok (Victor Shepelev) almost 3 years ago
it would be nice to have syntax that allows to shorten one-line matchers for predicates where they would be commonly used
TBH, since pattern matching inception I hope for some way of putting patterns into values—to store them in constants, and, in that case, simple grep
will do (if that value would respond to #===
which it should!):
MY_PATTERN = _pm_(first_name: /^J/, age: 18..)
# ...and then
if value in MY_PATTERN ...
# ...and, consequently,
people.grep(_pm_(first_name: /^J/, age: 18..))
(I am marking the dreamed-of PM constructor as ugly _pm_
here to underline it is not a ready proposal, but "something should be here")
Updated by Dan0042 (Daniel DeLorme) almost 3 years ago
zverok (Victor Shepelev) wrote in #note-11:
TBH, since pattern matching inception I hope for some way of putting patterns into values
Close enough?
MY_PATTERN = proc{ _1 in {name: /^B/, age: 18..} }
people = [{:name=>"Jim", :age=>18}, {:name=>"Bob", :age=>40}]
people[0] in MY_PATTERN #=> false
people[1] in MY_PATTERN #=> true
people.grep(MY_PATTERN) #=> [{:name=>"Bob", :age=>40}]
Updated by zverok (Victor Shepelev) almost 3 years ago
Close enough?
Obviously :) But still a "hack". E.g. it is not a "value representing the pattern", this way we can talk ourselves into "we didn't need PM at all, we always could
MY_PATTERN = -> { _1[:name] =~ /^B/ && _1[:age] > 18 }
# ...and
case foo
when -> { _1[:name] =~ /^B/ && _1[:age] > 18 }
:shrug: