Feature #6483
closedparametric map
Description
I found very common use-case for map: map with parameters and &:meth syntax. For example:
matrix =[[1,2,3],[4,5,6]]
matrix.pmap(' ',&:join).join("\n") # => "1 2 3\n4 5 6
[1,2,3,4,5].pmap(2, &:to_s) # ['1', '10', '11', '100', '101']
[1,2,3,4,5].pmap(&:to_s) # ['1', '2, '3', '4', '5'] # empty parameter list behaves as usual map
Isn't it much better than ugly and verbose code:
matrix.map{|line| line.join(' ')}.join("\n")
I can write simple implementation
class Proc
def curry_except_self(*args)
Proc.new{|slf| curry[slf,*args] }
end
end
module Enumerable
def pmap!(*args,&block)
map! &block.curry_except_self(*args)
end
def pmap(*args,&block)
dup.pmap!(*args, &block)
end
end
Use-cases can be rewritten as tests (I can send my own unit-test if needed)
Also I've two related things to discuss.
- First is &-syntax. Is it possible to change ruby-interpeter in such a way that &:proc could be at any place. matrix.pmap(&:join,' ') is much prettier than matrix.join(' ',&:join) What is the reason behind this restriction? And if one can remove this restriction, we'd have new nice syntax.
- I'm not very experience in functional programming with curry etc, but it seems to me that currying proc without supplying first argument(self) can be common task when &:meth syntax is in play. If so, may be my curry_except_self(*args) also should be included in ruby.
Updated by prijutme4ty (Ilya Vorontsov) over 12 years ago
Also I propose similar approach for tap
method.
class Object
def ptap(*args,&block)
tap &block.curry_except_self(*args)
end
end
So instead of
lines = File.read('filename.txt');
lines.delete('')
or
lines = File.read('filename.txt').tap{|x|x.delete ''}
I can use
lines = File.read('filename.txt').ptap('', &:delete)
Updated by nobu (Nobuyoshi Nakada) over 12 years ago
I proposed a different approach, symbol with arguments syntax.
(1..5).map(&:to_s(2)) #=> ['1', '10', '11', '100', '101']
And another proposed Symbol#call
in the same thread, instead.
(1..5).map(&:to_s.(2)) #=> ['1', '10', '11', '100', '101']
Updated by prijutme4ty (Ilya Vorontsov) over 12 years ago
nobu (Nobuyoshi Nakada) wrote:
I proposed a different approach, symbol with arguments syntax.
(1..5).map(&:to_s(2)) #=> ['1', '10', '11', '100', '101']
And another proposed
Symbol#call
in the same thread, instead.(1..5).map(&:to_s.(2)) #=> ['1', '10', '11', '100', '101']
Really cool syntax! I didn't even thought that about such a way. Second version I wrote like that:
class Symbol
def call(*args)
obj=Object.new.instance_exec(self,args){|sym,params| @sym=sym; @args = params; self}
obj.define_singleton_method :to_proc do
@sym.to_proc.curry_except_self(*@args)
end
obj
end
end
Unfortunately symbol isn't cloneable, so I used auxiliary object
In such a way one shouldn't define both tap and map and pleorth of other methods!
But first approach you suggested cannot be implemented in ruby yet. Hope sometimes ruby'll supply such a syntax! I propose it shouldn't even have parentheses for args
Updated by Eregon (Benoit Daloze) over 12 years ago
nobu (Nobuyoshi Nakada) wrote:
I proposed a different approach, symbol with arguments syntax.
(1..5).map(&:to_s(2)) #=> ['1', '10', '11', '100', '101']
And another proposed (({Symbol#call})) in the same thread, instead.
(1..5).map(&:to_s.(2)) #=> ['1', '10', '11', '100', '101']
For info, this is http://bugs.ruby-lang.org/issues/show/4146 (I had a hard time finding it back).
Another syntax is proposed by Koichi (from what I can understand):
p %w[12 45 32].map(&PM.to_i(9)).map(&PM * 2)
p %w[abc def ghi].map(&PM[1])
Which might be simplified to:
p %w[12 45 32].map(&.to_i(9)).map(& * 2)
p %w[abc def ghi].map(&[1])
I really like that one.
Updated by trans (Thomas Sawyer) over 12 years ago
p %w[12 45 32].map(&.to_i(9)).map(& * 2)
That's pretty neat. I wonder about its implementation, so basically &
becomes a special object that returns a proc when method is called on it?
class Ampersand < BasicObject
def method_missing(s, *a, &b)
::Proc.new{ |x| x.public_send(s, *a, &b) }
end
end
& = Ampersand.new
This is also interesting in that it has an appearance similar to an anaphora (default block argument):
p %w[12 45 32].map{it.to_i(9)}.map{it * 2}
But despite appearances they are very different in nature.
Updated by prijutme4ty (Ilya Vorontsov) over 12 years ago
Eregon (Benoit Daloze) wrote:
Another syntax is proposed by Koichi (from what I can understand):
p %w[12 45 32].map(&PM.to_i(9)).map(&PM * 2) p %w[abc def ghi].map(&PM[1])
Which might be simplified to:
p %w[12 45 32].map(&.to_i(9)).map(& * 2) p %w[abc def ghi].map(&[1])
I really like that one.
It's fine, but in version with PM it look like a hack. However if it'll be implemented in second version - I'd take my words back.
Updated by mame (Yusuke Endoh) over 12 years ago
- Status changed from Open to Assigned
- Assignee set to matz (Yukihiro Matsumoto)
Assigning to matz.
Eregon (Benoit Daloze) wrote:
For info, this is http://bugs.ruby-lang.org/issues/show/4146 (I had a hard time finding it back).
Thanks Benoit! I added it as a related ticket.
--
Yusuke Endoh mame@tsg.ne.jp
Updated by prijutme4ty (Ilya Vorontsov) over 12 years ago
I've made a simple extension that allows one to use nested symbolic-procs like this:
[[1,2,3],[4,5,6]].map(&:map.(&:to_s.(2))) # => [['1','10','11'],['100','101','110']]
Not to duplicate code - look at http://bugs.ruby-lang.org/issues/4146#change-26991
Updated by nobu (Nobuyoshi Nakada) over 12 years ago
In golf_prelude.rb
:
class Symbol
def call(*args, &block)
proc do |recv|
recv.__send__(self, *args, &block)
end
end
end
It might use public_send
, indeed.
Updated by prijutme4ty (Ilya Vorontsov) over 12 years ago
Thank you for much more elegant code. It works with all of my specs except one:
['abc','cdef','xy','z','wwww'].select(&:size.() == 4) # ==> ['cdef', 'wwww']
It looks that one cannot make this work without proxy object.
Updated by mame (Yusuke Endoh) almost 12 years ago
- Target version set to 2.6
Updated by matz (Yukihiro Matsumoto) almost 12 years ago
- Status changed from Assigned to Rejected
I reject the original idea of #pmap which might be read as 'parallel map' or 'parametric map' or something else.
The idea of adding parameter to block with argument specified e.g. a.map(&:to_s.(2)) is interesting,
but it must be proposed in separated issue.
Matz.