Feature #11690
openUpdate Hash during multiple assignment
Added by danielpclark (Daniel P. Clark) about 9 years ago. Updated almost 9 years ago.
Description
Given that we can assign multiple variables at once
a,b,c = 1,2,3
It would be nice to be able to update a Hash during multiple assignment rather than replacing it. Currently
x = {a: 1, b: 2}
x, y ,z = {c: 3}, 6, 7
x
# => {c: 3}
What I propose is adding Hash#update=
to permit updating during multiple assignment.
class Hash
def update=(h)
update(h)
end
end
x = {a: 1, b: 2}
x.update, y ,z = {c: 3}, 6, 7
x
# => {a: 1, b: 2, c: 3}
This would be most useful in scenarios where a method or proc return multiple values. When the method returns the values we don't normally know the key outside where the hash assignment is.
example = proc { [{:hi => :hello}, 5] }
hash = {}
# Currently in Ruby with an Unknown key multiple assignment isn't an option
hash[????], current = example.call
# We currently have to two step it
result, current = example.call
hash.update(result)
But with Hash#update=
we don't have to know the key.
Updated by phluid61 (Matthew Kerwin) about 9 years ago
On 16/11/2015 2:32 AM, 6ftdan@gmail.com wrote:
This would be most useful in scenarios where a method or proc return
multiple values. When the method returns the values we don't normally know
the key outside where the hash assignment is.example = proc { [{:hi => :hello}, 5] } hash = {} # Currently in Ruby with an Unknown key multiple assignment isn't an
option
hash[????], current = example.call
We currently have to two step it¶
result, current = example.call
hash.update(result)But with `Hash#update=` we don't have to know the key.
I get the use-case, but I think the understandability of the code starts to
suffer.
What about something completely new but splat-like?
hash[*], current = example.call
This is even better when the hash comes last, it looks more like an options
parameter:
opts = get_default_hash
a, b, opts[*] = example2.call
I would assume this also works in single assignment:
opts = get_default_hash
opts[*] = get_new_opts
Updated by danielpclark (Daniel P. Clark) about 9 years ago
I'm not sure the opts[*]
is a good idea. You have have current splat behavior with multiple assignment.
a, *, c = [1,2,3,4,5]
a
# => 1
c
# => 5
And the opts[*]
is unclear about what it is and what it expects. It could be a Proc
.
prc = proc {|b,c,d| b+c+d }
# if [*] were implemented
a, prc[*], e = [1,2,3,4,5]
As far as I know Hash
is the only core object with an update
method. And in other cases where update
is used, such as in Rails' ActiveRecord, a Hash
is the expected input. So when the update
method is called it is generally understood to be expecting a Hash
object.
Implementing [*]
would be much more work that simply implementing Hash#update=
I was thinking about suggesting Hash#merge!=
as well but Ruby syntax does not allow the equals sign after and exclamation point. So I have chosen to only suggest Hash#update=
class Hash
def update=(h)
update(h)
end
end
Updated by phluid61 (Matthew Kerwin) about 9 years ago
On 17/11/2015 12:18 AM, 6ftdan@gmail.com wrote:
Issue #11690 has been updated by Daniel P. Clark.
I'm not sure the
opts[*]
is a good idea. You have have current splat
behavior with multiple assignment.a, *, c = [1,2,3,4,5] a # => 1 c # => 5
And the
opts[*]
is unclear about what it is and what it expects. It
could be aProc
.prc = proc {|b,c,d| b+c+d } # if [*] were implemented a, prc[*], c = [1,2,3,4,5]
Good point. I won't suggest anything to do with hash-splatting **
;)
As far as I know
Hash
is the only core object with anupdate
method. And in other cases whereupdate
is used, such as in Rails'
ActiveRecord, aHash
is the expected input. So when theupdate
method
is called it is generally understood to be expecting aHash
object.Implementing
[*]
would be much more work that simply implementing
Hash#update=
I was thinking about suggesting
Hash#merge!=
as well but Ruby syntax
does not allow the equals sign after and exclamation point. So I have
chosen to only suggestHash#update=
class Hash def update=(h) update(h) end end
It's still very strange to think of 'update
' as a property of a hash
object. Without some kind of new syntactic construct, with its own clear
semantics, this seems like a hack and doesn't feel right in the core (to
me; others may disagree.)
Updated by nobu (Nobuyoshi Nakada) about 9 years ago
Just an idea.
class ToBeAssigned
def initialize(obj)
@target = obj
end
def method_missing(m, *args)
@target.__send__(m.to_s.chomp('='), *args)
end
def respond_to_missing?(m)
@target.respond_to?(m.to_s.chomp('='))
end
end
module Kernel
def to_be_assigned_with
ToBeAssigned.new(self)
end
end
x = {a: 1, b: 2}
x.to_be_assigned_with.update, y ,z = {c: 3}, 6, 7
p x
Updated by danielpclark (Daniel P. Clark) about 9 years ago
Nobuyoshi Nakada wrote:
Just an idea.
x.to_be_assigned_with.update, y ,z = {c: 3}, 6, 7
I absolutely love the extensibility of this! In that it can be used on any object to allow multiple assignment with any method. Brilliant Nobuyoshi! Now if only the method name wasn't so long ;-)
Maybe we could replace to_be_assigned_with
to slurp
, consume
, devour
, or injest
;-) It properly expresses the additive nature of the action. Oooh! Or even feed_me
. eg) x.feed_me.update
... Although I don't think most of those would make it in as an official method in Ruby; they're a bit silly.
Updated by nobu (Nobuyoshi Nakada) about 9 years ago
Daniel P. Clark wrote:
Maybe we could replace
to_be_assigned_with
toslurp
,consume
,devour
, orinjest
;-)
My dictionary doesn't have the last word, maybe a compound word from inject
and ingest
?
Updated by danielpclark (Daniel P. Clark) about 9 years ago
You're right! I misspelled it. If I had added a space "in jest" it would be "a joke". ingest
is what I had meant to say. I wrote how it is pronounced rather than how it is spelled.
Updated by danielpclark (Daniel P. Clark) about 9 years ago
I was thinking of what word(s) may best clarify this and not conflict with other potential uses. One issue about the word assigned
is it seems like self =
or replace
. I at first was thinking of to_be
... but that's sounds more like a promise of the future and each method would have to end in ed
like so x.to_be.updated = a: 1
. To avoid using a promise form and complex ed
handling I was thinking of just to
; x.to.update
. But I don't think this is clear enough. So I have found the shortest thing I can think of that matches the statement to_be_assigned_with
, and that is apply
. It's short, simple, clear, and reveals to anyone that more is being done here.
To copy from Nobuyoshi's idea
class Applicable
def initialize(obj)
@target = obj
end
def method_missing(m, *args)
@target.__send__(m.to_s.chomp('='), *args)
end
def respond_to_missing?(m)
@target.respond_to?(m.to_s.chomp('='))
end
end
module Kernel
def apply
Applicable.new(self)
end
end
x = {a: 1, b: 2}
x.apply.update, y ,z = {c: 3}, 6, 7
p x
Arguably I think this is just as clear as to_be_assigned_with
, and is different enough for people to notice that something else is going on here. That should be enough.
Updated by danielpclark (Daniel P. Clark) about 9 years ago
If we wanted to avoid chomping the equals sign on methods that have the equals already we'd have to write in a check for that. But I'm not sure that would be as performant.
class Applicable
def initialize(obj)
@target = obj
end
def method_missing(m, *args)
m = @target.respond_to?(m) ? m : m.to_s.chomp('=')
@target.__send__(m, *args)
end
def respond_to_missing?(m)
@target.respond_to?(m.to_s.chomp('='))
end
end
module Kernel
def apply
Applicable.new(self)
end
end
x = {a: 1, b: 2}
x.apply.update, y ,z = {c: 3}, 6, 7
x
# => {:a=>1, :b=>2, :c=>3}
# Hash still responds to :[]= method now after :apply
x.apply[:z] = 0
x
# => {:a=>1, :b=>2, :c=>3, :z=>0}
I think the performance isn't an issue for this change. Anyone who uses the apply
method should expect the added behaviors performance hit.
Updated by danielpclark (Daniel P. Clark) almost 9 years ago
Even shorter Kernel#with
my_hash = {}
my_hash.with.update, b, c = ->{ {a: 1}, 5, 6 }.call
Updated by danielpclark (Daniel P. Clark) almost 9 years ago
I made a gem for this. gem assign
I figure Kernel#assign is the best method name for this and the object it returns is an Assignable object.