The lack of this syntax kind of limits the ability to design DSLs in Ruby in my opinion. I don't think this would bring any conflicts with existing parser rules.
My proposal would be to put the value at the last argument, akin to how []= works. So, for example this code would work:
The kwargs may be supported similarly to how they work on []=, ie. becoming a penultimate Hash argument. While maybe not perfect, it is consistent with how []= works and I imagine most usecases won't require kwargs.
Notice that, while [] in []= cannot be omitted, () in method invocation can be omitted. Are you expecting () in the proposed feature to be omittable or not? Either way, I think it would introduce complexity. I think that is a crucial difference between []= and the proposed feature; the latter is not as simple as the former.
I believe in this particular case it would make more sense to have a dual method to dig, rather than adding additional complexity to the syntactic sugar around =. There were proposals in the past for Enumerable#bury that targeted this behavior:
moduleEnumerabledefbury(*paths,&value_fn)returnunlessblock_given?*lead,target=pathsiflead.empty?# Single item in pathself[target]=yield(self[target])elseabove_value=self.dig(*lead)above_value[target]=yield(above_value[target])endendendcollection=[{a: 1,b: 2,c: [{d: 3}]}]# => [{:a=>1, :b=>2, :c=>[{:d=>3}]}]collection.bury(0,:c,0,:d){|v|v+5}# => 8collection# => [{:a=>1, :b=>2, :c=>[{:d=>8}]}]
The difficulty for such functions is how to differentiate between the varadic path and the value setter. For me I believe block functions strike a good medium here, and may be a viable solution, though Matz has previously rejected the idea of adding a bury function. Perhaps it may make sense to bring it up again if this is compelling, as it does bear some similarity to merge behavior.
@sawa (Tsuyoshi Sawada) - My proposal would be to allow omitting parentheses only if there are no arguments provided, ie. how it is currently.
self.xyz=6# correct currentlyself.xyz()=6# correct under the proposalself.xyz(a)=6# correct under the proposalself.xyza=6# parser conflict, it is already a correct code meaning something else
I am not very familiar with MRI code unfortunately, so I can't estimate if this will introduce a lot of complexity or not. Certainly lvalue will need to accept a lot more types of expression. I develop an alternative Ruby implementation (Opal), though not its parser, and there I have a clear path for implementation of this feature.
A similar, though a little different, feature exists in C, where *get_mem(123) = 123 is a correct code.
Another argument for this feature is that it's easy to pass an arbitrary number of arguments to any operator, like +, *, []=, [], something?, something!. From what I know, something= is the only kind of operator that needs __send__ to pass other number of arguments than 1 (self.x=(1,2,3) is not a correct code, perhaps it could be an alternative to accept it, instead of this proposal, but I assume it will be a lot harder).
I happen to sometimes want to add an optional argument to setter and it ends up with a code refactor that makes code a lot more complicated, needing methods like set_x, while I can easily add arguments to a getter.
The difficulty for such functions is how to differentiate between the varadic path and the value setter
While block adds a lot of flexibility for certain cases, like bury, this proposal more clearly separates what's a variadic path and what's a value (following the semantics of [] and []= operators). Perhaps my pseudo-code with dig= isn't the greatest idea, but it demonstrates the concept.
A more real life example happened in my code. I wanted to create an API like the following:
entity.attribute(3)# => value of attribute 3entity.attribute("attrtype 3 by name")# => value of attribute 3entity.attribute(AttrType.new(3))# => value of attribute 3entity.attribute(3)=10entity.attribute("attrtype 3 by name")=10entity.attribute(AttrType.new(3))=10
Of course I ended up with extending Hash to accurately resolve the hash keys. Instead of the API described above, I created this:
entity.attributes[3]# => value of attribute 3entity.attributes["attrtype 3 by name"]# => value of attribute 3entity.attributes[AttrType.new(3)]# => value of attribute 3entity.attributes[3]=10entity.attributes["attrtype 3 by name"]=10entity.attributes[AttrType.new(3)]=10
While I achieved the same goal by it, the resulting API implementation added a lot of complexity.