Project

General

Profile

Feature #16264

Updated by nobu (Nobuyoshi Nakada) 8 months ago

It is a part of thinking about the "argument-less call style" I already made several proposals about. 

 ### Preface **Preface** 

 ***Argument-less call style*** is what I now call things like `map(&:foo)` and `each(&Notifications.:send)` approaches, and I believe    that naming the concept (even if my initial name is clamsy) will help to think about it. After using it a lot on a large production codebase (not only symbols, but method references too, which seem to be less widespread technique), I have a strong opinion that it not just "helps to save the keypresses" (which is less important), but also helps to clearer separate the concepts on a micro-level of the code. E.g. if you feel that `each(&Notifications.:send)` is "more right" than `select { |el| Notifications.send(el, something, something) }`, it makes you think about `Notifications.send` design in a way that allows to pass there _exactly_ that combination of arguments so it would be easily callable that way, clarifying modules responsibilities. 

 > (And I believe that "nameless block parameters", while helping to shorter the code, lack this important characteristic of clarification.) 

 ### The problem **The problem** 

 One of the problems of "argument-less calling" is passing additional arguments, things like those aren't easy to shorten: 
 ```ruby 
 ary1.zip(ary2, ary3).map { |lines| lines.join("\n") } 
 #                                               ^^^^ 
 construct_url.then(&HTTP.:get).body.then { |text| JSON.parse(text, symbolize_names: true) } 
 #                                                                    ^^^^^^^^^^^^^^^^^^^^^^ 
 ``` 

 (BTW, here is [a blog post](https://zverok.github.io/blog/2019-10-18-each_with_object.html) where I show recently found technique for solving this, pretty nice and always existing in Ruby, if slightly esotheric.) 

 There's a lot of proposals for "partial applications" which would be more expressive than `.curry` ([guilty](https://bugs.ruby-lang.org/issues/16113) [myself](https://bugs.ruby-lang.org/issues/15301)), but the problematic part in all of this proposals is: 

 > **The most widespread "shortening" is `&:symbol`, and `Symbol` itself is NOT a functional object, and it is wrong to extend it with functional abilities.** 

 One of consequences of the above is, for example, that you can't use 2.6's proc combination with symbols, like `File.:read >> :strip >> :reverse`. You want, but you can't. 

 Here (while discussing aforementioned blog posts), I stumbled upon an idea of how to solve this dilemma. 

 ### The proposal **The proposal** 

 I propose to have a syntax for creating a functional object that when being called, sends the specified method to its first argument. Basically, what `Symbol#to_proc` does, but without "hack" of "we allow our symbols to be convertible to functional objects". Proposed syntax: 

 ```ruby 
 [1, 2, 3].map(&.:to_s) 
 ``` 

 Justification of the syntax: 

 * It is like `Foo.:method` (producing functional object that calls `method`) 
 * Orphan `.:method` isn't allowed currently (you need to say `self.:method` to refer to "current `self`s method"), and Matz's justification was "it would be too confusable with `:method`, small typo will change the result" -- which in PROPOSED case is not as bad, as `:foo` and `.:foo` both meaning the same thing; 
 * It looks kinda nice, similar to ([proposed and rejected](https://bugs.ruby-lang.org/issues/16120)) `map { .to_s }` → with my proposal, it is `map(&.:to_s)`, implying somehow applying `.to_s` to the previous values in the chain. 

 The behavior: `.:foo` produces object of class, say, `MethodOfArgument` (class name is subject to discuss) — which makes differences of "Proc created from Symbol" (existing internally, but almost invisible) obvious and hackable. 

 Now, to this object it is _natural_ to apply things like currying (even today's wordy one, but probably better options will come when they'll be able to be uniformly applied to all "shortcut" objects). 

 **Transition:** `:foo` and `.:foo` could work similarly for some upcoming versions (or indefinitely), with `.:foo` being more powerful alternative, allowing features like `groups_of_lines.map(&.:join.with(' '))` or something. 

 It would be like "real" and "imitated" keyword arguments. "Last hash without braces" was good at the beginning of the language lifecycle, but then it turned out that real ones provide a lot of benefits. Same thing here: `&:symbol` is super-nice, but, honestly, it is semantically questionable, so may be slow switch to a "real thing" would be gainful for everybody?..

Back