Feature #8987

map/collect extension which handles arguments

Added by So Wieso 7 months ago. Updated 3 months ago.

[ruby-core:57679]
Status:Open
Priority:Normal
Assignee:-
Category:core
Target version:current: 2.2.0

Description

Please consider extending map/collect by allowing additional arguments to be passed to proc, like:
A: [1,2,3,4].map :+, 4
and/or
B: [1,2,3,4].map 4, &:+

=> [5, 6, 7, 8]

Variant A is probably more readable. Variant B is more versatile (allows arbitrary arguments to be passed to block).

mappi.rb Magnifier - sample implementation in ruby (called mappi) (410 Bytes) So Wieso, 10/05/2013 11:02 PM

History

#1 Updated by Tsuyoshi Sawada 7 months ago

=begin
In case of commutative operations like +, you can do it like this:

[1, 2, 3, 4].map(&4.method(:+))
# => [5, 6, 7, 8]

=end

#2 Updated by Hiroshi SHIBATA 3 months ago

  • Target version changed from 2.1.0 to current: 2.2.0

#3 Updated by Koichi Sasada 3 months ago

(2013/10/06 0:26), sawa (Tsuyoshi Sawada) wrote:

[1, 2, 3, 4].map(&4.method(:+))
# => [5, 6, 7, 8]

Interesting.

If we use λ (alias of lambda), it is more short.

 module Kernel
   alias λ lambda
 end
 p [1, 2, 3, 4].map(&4.method(:+)) #=> [5, 6, 7, 8]
 p [1, 2, 3, 4].map(&λ{|x| 4+x})  #=> [5, 6, 7, 8]

If we define λ as the following definition, more short code.

 module Kernel
   def λ(a, sym)
     lambda{|x| a.send(sym, x)}
   end
 end

 p [1, 2, 3, 4].map(&λ(4, :+))  #=> [5, 6, 7, 8]

A bit shorter version.

 module Kernel
   def λ(expr)
     eval("lambda{|x| #{expr} x}")
   end
 end

 p [1, 2, 3, 4].map(&λ("4+"))  #=> [5, 6, 7, 8]

If we have default parameter `_' (maybe matz doesn't like), we can make
more short code.

 p [1, 2, 3, 4].map(&λ{4+_})      #=> [5, 6, 7, 8]

Summary:
p [1, 2, 3, 4].map(&4.method(:+)) #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ{|x| 4+x}) #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ(4, :+)) #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ("4+")) #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ{4+_}) #=> 5, 6, 7, 8

--
// SASADA Koichi at atdot dot net

#4 Updated by Matthew Kerwin 3 months ago

On 31 January 2014 15:48, SASADA Koichi ko1@atdot.net wrote:

p [1, 2, 3, 4].map(&4.method(:+)) #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ{|x| 4+x})  #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ(4, :+))    #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ("4+"))     #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ{4+_})      #=> [5, 6, 7, 8] (doesn't run)

Are any of these actually better than:

 p [1, 2, 3, 4].map{|x| 4+x }

?

--
Matthew Kerwin
http://matthew.kerwin.net.au/

#5 Updated by Koichi Sasada 3 months ago

Matthew Kerwin wrote:

Are any of these actually better than:

 p [1, 2, 3, 4].map{|x| 4+x }

?

LOL

#6 Updated by So Wieso 3 months ago

Matthew Kerwin wrote:

On 31 January 2014 15:48, SASADA Koichi ko1@atdot.net wrote:

p [1, 2, 3, 4].map(&4.method(:+)) #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ{|x| 4+x})  #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ(4, :+))    #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ("4+"))     #=> [5, 6, 7, 8]
p [1, 2, 3, 4].map(&λ{4+_})      #=> [5, 6, 7, 8] (doesn't run)

Are any of these actually better than:

 p [1, 2, 3, 4].map{|x| 4+x }

?

--
Matthew Kerwin
http://matthew.kerwin.net.au/

Actually I believe the most readable form would be

p [1,2,3,4].map{ 4 + _ } # when there is no |…|, set block params to _

Many blocks in realworld-code are so easy, that it is really a barrier to have to think about a name, and therefore one uses often non-verbose names like you used x. Where is the point in being forced to think of a name, when you don't set the name to something meaningful. I guess this was the idea why the to_proc convention was introduced. The problem with it is, that it is really limiting because you cannot use parameters (and the presence of a parameter doesn't necessarily make problems so complex to justify a name).

#7 Updated by Yusuke Endoh 3 months ago

Just joke.

p [0,1,2,3].dmap + 1 #=> [1, 2, 3, 4]

# %w(foo bar baz).map {|x| x.upcase.concat("!") }
p %w(foo bar baz).map_do.upcase.concat("!").end
  # => ["FOO!", "BAR!", "BAZ!"]

Source:

class DelegateMap < BasicObject
  def initialize(enum)
    @enum = enum
  end
  def method_missing(mhd, *args, &blk)
    @enum.map {|elem| elem.__send__(mhd, *args, &blk) }
  end
end
class CascadingDelegateMap < BasicObject
  def initialize(enum)
    @enum = enum
  end
  def method_missing(mhd, *args)
    ::CascadingDelegateMap.new(@enum.map {|elem| elem.send(mhd, *args) })
  end
  def end
    @enum
  end
end
module Enumerable
  def dmap
    DelegateMap.new(self)
  end
  def map_do
    CascadingDelegateMap.new(self)
  end
end

Yusuke Endoh mame@tsg.ne.jp

#8 Updated by Matthew Kerwin 3 months ago

On Jan 31, 2014 6:20 PM, sowieso@dukun.de wrote:

Actually I believe the most readable form would be

p [1,2,3,4].map{ 4 + _ } # when there is no |...|, set block params to _

Many blocks in realworld-code are so easy, that it is really a barrier to
have to think about a name, and therefore one uses often non-verbose names
like you used x. Where is the point in being forced to think of a name,
when you don't set the name to something meaningful. I guess this was the
idea why the to_proc convention was introduced. The problem with it is,
that it is really limiting because you cannot use parameters (and the
presence of a parameter doesn't necessarily make problems so complex to
justify a name).

I guess you can solve it by syntax or by convention. I've, personally,
never had a pause when calling it 'x' or 'item', depending on the context.
You could also develop the convention of: arr.map{|_| ... }

My only problem with magic variables is that I can never remember when they
get (re)assigned. It's particularly annoying in perl, because those guys
never assign a variable when $_ will suffice.

Matthew Kerwin

#9 Updated by Tsuyoshi Sawada 3 months ago

Probably, it makes more sense to extend the syntax of Symbol#to_proc. The conventional Symbol#to_proc does not take an argument:

:foo.to_proc # => ->(x){x.foo}

My proposal is to let it take optional arguments that would be passed to the method within the created proc:

:foo.to_proc(y) # => ->(x){x.foo(y)}

So that

:+.to_proc(4) # => ->(x){x + 4}
[1, 2, 3, 4].map(&:+.to_proc(4)) # => [5, 6, 7, 8]

Not sure if any better than writing the original, but looks consistent.

Or, maybe we can use the method name Symbol#call, which is aliased to short forms, so that we can do:

:+.call(4) # => ->(x){x + 4}
:+.(4) # => ->(x){x + 4}
[1, 2, 3, 4].map(&:+.(4)) # => [5, 6, 7, 8]

#10 Updated by Henry Maddocks 3 months ago

Tsuyoshi Sawada wrote:

Probably, it makes more sense to extend the syntax of Symbol#to_proc. The conventional Symbol#to_proc does not take an argument:

+1

Also available in: Atom PDF