Project

General

Profile

Feature #16264

Updated by zverok (Victor Shepelev) 7 months ago

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

 ### 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 

 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 

 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:** 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. 

 ### Potential gains 

 * New Now, to this object could be used in proc composition: `File.:read >> .:strip >> JSON.:parse >> .:compact` 
 * When both "method" and "method of argument" are proper functional objects, a new partial application syntax can be discussed, common for them both. For example (but **not necessary this method name!**) 

   ```ruby 
 paragraph_hashes.map(&.:merge.with(author: current_author)) 
 filenames.map(&File.:read.with(mode: 'rb')) 
   ``` 
 * (I believe at this point we'll be able it is _natural_ to finally switch from discussing "show we extend Symbol with more callable-alike functionality" to just method's name and exact behavior) 
 * I am not an expert, apply things like currying (even today's wordy one, but probably some optimizations could better options will come when they'll be applied, too 
 * Currently, `:sym.to_proc` is internally different from other proc, but this can't able to be introspected: 

   ```ruby 
 :read.to_proc.inspect # => "#<Proc:0x0000556216192198(&:read)>" 
                                                      # ^^^^^ 
   ``` 
 *    Probably, exposure of this fact could lead uniformly applied to some new interesting metaprogrammin/optimization techniques. all "shortcut" objects). 

 ### Transition 

 **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.partial_apply(' `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