Feature #15483
closedProc or Method combination with Symbol
Description
In [Feature #6284], Matz said
We need more discussion if we would add combination methods to the Symbol class.
Right, let's get started to discuss.
For your information, recent a few months I'm discussing this with @osyo .
This is a discussion of "design"¶
I understand that all features of this issue have both merits and demerits, but I guess that language design is most important. All features of this issue related to each other.
Abstract¶
At present, you can use Proc#>> or Proc#<< with Symbol#to_proc.
%w{72 101 108 108 111}.map(&(:to_i.to_proc >> :chr.to_proc))
# => ["H", "e", "l", "l", "o"]
This is convenient but methods that take block can take a proc with & syntax sugar instead of #to_proc by right, like [1, 2, 3].map(&:to_s). So Symbol#to_proc looks like too long for Proc#>> or Proc#<<. Therefore, you need new syntax sugar.
Receiver¶
Symbol#>> and Symbol#<<
¶
Symbol#>> and Symbol#<< will be considered, but this means that Symbol is treated as Proc partially. The [1, 2, 3].map(&:to_s) treats Symbol as Proc partially too, but it's with pre-positioned &.
%w{72 101 108 108 111}.map(&(:to_i >> :chr.to_proc))
# => ["H", "e", "l", "l", "o"]
I can't come up with other ideas for the Symbol receiver.
New &:symbol_name syntax sugar for :symbol_name.to_proc
¶
%w{72 101 108 108 111}.map(&(&:to_i >> :chr.to_proc)))
# => ["H", "e", "l", "l", "o"]
Argument¶
Calls #to_proc by Proc#>> or Proc#<< internally as a duck typing¶
%w{72 101 108 108 111}.map(&(:to_i.to_proc >> :chr))
# => ["H", "e", "l", "l", "o"]
In this case, Proc#>>(:to_i.to_proc >>) calls Symbol#to_proc(for :chr) inside.
This is useful to use with Hash#to_proc:
h = { Alice: 30, Bob: 60, Cris: 90 }
%w{Alice Bob Cris}.map(&(:to_sym.to_proc >> h))
# => [30, 60, 90]
Proc#>> and Proc#<< take block as an argument¶
%w{72 101 108 108 111}.map(&(:to_i.to_proc >> &:chr))
Combination of receiver and argument¶
Symbol#>> and calling #to_proc internally:
%w{72 101 108 108 111}.map(&(:to_i >> :chr))
# => ["H", "e", "l", "l", "o"]
&:symbol_name syntax sugar for :symbol_name.to_proc and Symbol#>> and taking block:
%w{72 101 108 108 111}.map(&(&:to_i >> &:chr))
# => ["H", "e", "l", "l", "o"]
  
        
          
          Updated by aycabta (aycabta .) almost 7 years ago
          
          
        
        
      
      - Tracker changed from Bug to Feature
 - Backport deleted (
2.4: UNKNOWN, 2.5: UNKNOWN, 2.6: UNKNOWN) 
        
          
          Updated by aycabta (aycabta .) almost 7 years ago
          
          
        
        
      
      - Description updated (diff)
 
        
          
          Updated by shevegen (Robert A. Heiler) almost 7 years ago
          
          
        
        
      
      I am biased so I do not want to digress from this thread too much while explaining my bias. However had,
I still want to state a few things:
- In regards to Symbol, this is a language design decision, how Symbols are to be used. I think we can
have valid arguments for both main variants, e. g. to keep Symbols simple, or to allow more flexibility.
Personally I'd rather prefer them simple, largely because I don't feel most proposals for change make
them better and most definitely not prettier; but I have no real problem either way here. 
Still, in regards to proposals allowing for more flexibility of Symbols, this leads me to:
- Syntax consideration. To me personally the proposed syntax is not very elegant.
 
In particular:
.map(&(&:to_i >> &:chr))
Is really not pretty. We use '& three' times there; and the new >>. It does not really feel consistent
with other parts of ruby in my opinion, syntax-wise alone. I have less of a problem with a single & but
I also dislike that I have to look carefully, e. g to distinguish between a** .map(&:)** versus a .map(&)
variant. Do we really want to have to look for & now carefully and a : or no :, on top of it? The second
variant also packs a lot more information into the method-call, which makes it a bit hard to see what
is going on to me, e. g. .map(&(&:to_i >> :chr.to_proc))). And the >> which I am also not a big fan of,
but as said in the beginning, I am biased already, so my comments will be biased as well.
- Another issue I have, and this is more general, that I do not really see the massive benefit. This is not
solely confined to the proposal here, and is obviously subject to personal opinion/evaluation and how
you use ruby ("more than one way to use ruby", too), but more generally about some other related
proposals too, where I am not really sure if the change is needed or provides a lot of really useful
things that we need. 
I understand it if the goal is more flexibility in what we can do; for example, I think I also stated before
that I am in agreement with proposals to allow arguments to methods given rather than solely be able
to use e. g.  .map(&:method SOME WAY FOR ARGUMENTS HERE). The major problem I have with most
of these proposals I have seen so far is syntax-wise. We do not have that many characters while staying
in ASCII land, but the core of ruby is very elegant and quite simple, syntax-wise (for me). Several of the
proposals in the last ~3 years or so, are, to me, syntax-wise, not really elegant. Syntax is not everything
but if I have to stare at code a lot then I'd rather look at good syntax than bad one.
Anyway, I'll close my comment here.
        
          
          Updated by osyo (manga osyo) almost 7 years ago
          
          
        
        
      
      I am thinking like this.
NOTE: Here we define it as follows.
- functional object
- defined 
#call(and#<<#>>) object - e.g. 
ProcMethod 
 - defined 
 - blockable object
- defined 
#to_procobject - e.g. 
SymbolHash 
 - defined 
 
Current¶
- 
Proc#<<andProc#>>arguments is functional object
call#call. - 
Proc#<<andProc#>>is not call#to_proc - 
Proc#<<andProc#>>is not accept block argument 
Composite function in Ruby¶
- Composite function is functional object and functional object
 - 
functional object >> functional object# => OK - 
functional object >> other object# => NG - 
other object >> functional object# => NG 
Symbol is functional object¶
- 
Symbolis blockable object - 
Symbolis not functional object - Handling 
Symbolwith compositing functions is incorrect - What about other blockable objects?
- e.g. 
Hash - 
Hashis functional object? 
 - e.g. 
 
Proc#<< is call #to_proc ?¶
- It should be explicitly converted to 
Proc(functional object) with# to_proc- 
proc << :hoge=> NG::hogeis notProc - 
proc << :hoge.to_proc=> OK : Explicitly convert:hogetoProc 
 - 
 - Same as not handling 
"42"as anInteger- 
1 + "42"=> NG :"42"is not anInteger - 
1 + "42".to_i=> OK : Explicitly convert"42"to aProc 
 - 
 
Proposal1 : Symbol to functional object¶
- define 
Symbol#>>Symbol#<<Symbol#call - What about other blockable objects?
- 
Hashis functional object? 
 - 
 - Is it really necessary for 
Symbol? - Is 
Symbolreally a "functinal object" ? 
# Symbol to functional object
class Symbol
	def call(*args, &block)
		to_proc.call(*args, &block)
	end
	def <<(other)
		to_proc << other
	end
	def >>(other)
		to_proc >> other
	end
end
p %w{72 101 108 108 111}.map(&(:to_i >> :chr))
# => ["H", "e", "l", "l", "o"]
Proposal2 : Symbol to functional object¶
- 
Proc#<<(other)toProc#<<(other, &block) - Prioritize 
other? 
class Proc
	prepend Module.new {
		def <<(other = nil, &block)
			# other or block?
			super(other || block)
		end
		def >>(other = nil, &block)
			# other or block?
			super(other || block)
		end
	}
end
# :to_i convert to Proc
# must be `.>>`
p %w{72 101 108 108 111}.map(&(:to_i.to_proc.>> &:chr))
# => ["H", "e", "l", "l", "o"]
Proposal3 : Define syntax sugar for #to_proc
¶
- For example, define 
#to_procto~@.- or other Unary operator
 - 
+@-@!&? 
 - Do not change current specifications
 - I think this is good
 
# Add ~@
class Object
	# ~ is to_proc
	# ~ or other unary operator?
	def ~@
		to_proc
	end
end
# Use Symbol#to_proc
p %w{72 101 108 108 111}.map(&(:to_i.to_proc >> :chr.to_proc))
# alias ~ is to_proc
p %w{72 101 108 108 111}.map(&~:to_i >> ~:chr)
Thank you :)
        
          
          Updated by nobu (Nobuyoshi Nakada) almost 7 years ago
          
          
        
        
      
      Why not using refinements?
# symbol/functionalized.rb
module Symbol::Functionalized
  refine(Symbol) do
    def call(*args, &block)
      to_proc.call(*args, &block)
    end
    def <<(other = (b = true), &block)
      to_proc << (b ? block : other.to_proc)
    end
    def >>(other = (b = true), &block)
      to_proc >> (b ? block : other.to_proc)
    end
  end
end
require 'symbol/functionalized'
using Symbol::Functionalized
p %w{72 101 108 108 111}.map(&:to_i >> :chr) #=> ["H", "e", "l", "l", "o"]
        
          
          Updated by osyo (manga osyo) almost 7 years ago
          
          
        
        
      
      hi, nobu :)
引用
Why not using refinements?
It is example code.
Also, Symbol#call is not called in Proc#<<.
# Error: undefined method `call' for :chr:Symbol (NoMethodError)
p %w{72 101 108 108 111}.map(&proc { |s| s.to_i } >> :chr) #=> ["H", "e", "l", "l", "o"]
        
          
          Updated by nobu (Nobuyoshi Nakada) almost 7 years ago
          
          
        
        
      
      # symbol/functionalized.rb
module Symbol::Functionalized
  refine(Symbol) do
    def call(*args, &block)
      to_proc.call(*args, &block)
    end
    def <<(other = (b = true), &block)
      to_proc << (b ? block : other.to_proc)
    end
    def >>(other = (b = true), &block)
      to_proc >> (b ? block : other.to_proc)
    end
  end
  refine(Proc) do
    def <<(other)
      super(other.to_proc)
    end
    def >>(other)
      super(other.to_proc)
    end
  end
end
        
          
          Updated by nobu (Nobuyoshi Nakada) almost 7 years ago
          
          
        
        
      
      I made function-composite gem, as a PoC.
        
          
          Updated by osyo (manga osyo) almost 7 years ago
          
          
        
        
      
      I think it will not work in the following cases.
# NG: Error undefined method `call' for :chr:Symbol (NoMethodError)
p (30.method(:+) >> :chr).call 42
h = { Alice: 30, Bob: 60, Cris: 90 }
# OK
p (:to_sym >> h).call "Alice"
# => 30
# NG
p (h << :to_sym).call "Bob"
Would you like to add Method#>> and Hash#>>, or other object #>> definitions?
I do not think that is good.
I think it is necessary to clearly separate "functional object"(e.g. Proc, Method) and "blockable object"(e.g. Symbol, Hash).
I think that it should handle only functional object in the composite function.
        
          
          Updated by mrkn (Kenta Murata) almost 7 years ago
          
          
        
        
      
      - Related to Bug #15428: Refactor Proc#>> and #<< added
 
        
          
          Updated by matz (Yukihiro Matsumoto) almost 7 years ago
          
          
        
        
      
      - Status changed from Open to Rejected
 
I feel the expression ary.map(&(:to_i << :chr)) is far less readable than ary.map{|x|x.to_i.chr}.
And the latter is faster and can take arguments NOW e.g. ary.map{|x|x.to_i(16).chr}.
Given these superiorities, this proposal does not sound attractive.
Matz.
p.s.
And this can lead to the default block parameter like it.