Cleaner syntax for .map (is there already a way, or ruby2 idea?)

I find that a bunch of my code looks like

   people.map{|person| person.email_addr}

where I use map to apply a single method to elements inside arrays.

That sure beats C, but wouldn't it be nice if there
were a shorthand for doing so. I think something
like this would be very nice:

   people[*].email_addr

Even better, I often have strings of such stuff that
looks like this, creating nested arrays

   departments.map{|dept| dept.people.map{|person| person.email_addr}}

where a much cleaner alternative would be

   departments[*].people[*].email_addr

so the "[*]" operator would descend into nested arrays.

In total ignorance of whether the parser would
allow it, I'd like to say that I think that'd
be a nice addition to ruby2 if there's no other
clean shorthand out there already.

Ron M wrote:

I find that a bunch of my code looks like

  people.map{|person| person.email_addr}

where I use map to apply a single method to elements inside arrays.

That sure beats C, but wouldn't it be nice if there
were a shorthand for doing so. I think something
like this would be very nice:

  people[*].email_addr

google for "symbol to_proc". For example:

http://extensions.rubyforge.org/rdoc/classes/Symbol.html

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Ron M:

I find that a bunch of my code looks like

  people.map{|person| person.email_addr}

where I use map to apply a single method to elements inside arrays.

I've done something like this before:

module Enumerable
  def map_send(*methods)
    map {|obj| methods.inject(obj) {|obj, meth| obj.send(meth) }}
  end
end
a = %w{ foo bar baz}
a.map_send(:capitalize, :reverse) #=> ["ooF", "raB", "zaB"]

.... I think something
like this would be very nice:

  people[*].email_addr

That's an interesting-looking operator there. I think it's very unclear what
it does. I'd prefer the mouse-poo version
  people.map { @.email_addr }
or
  people.map { it.email_addr }

I don't think Enumerable wants a special operator like this; method names
convey meaning.

Cheers,
Dave

Hi,

At Thu, 27 Oct 2005 15:05:41 +0900,
Ron M wrote in [ruby-talk:162876]:

That sure beats C, but wouldn't it be nice if there
were a shorthand for doing so. I think something
like this would be very nice:

   people[*].email_addr

What about:

  module Mappable
    class Mapper
      def initialize(obj)
        @obj = obj
      end
      def method_missing(meth, *args, &block)
        @obj.map {|i| i.send(meth, *args, &block)}
      end
    end
    def self.included(klass)
      super
      aref = klass.instance_method(:)
      klass.module_eval do
        define_method(:) do |*idx|
          if idx.empty?
            Mapper.new(self)
          else
            aref[*idx]
          end
        end
      end
    end
  end

  class Array
    include Mappable
  end

  a = %w[a b c]
  p a.upcase

···

--
Nobu Nakada

require 'facets/enumerable/op_mod'

  people.%.email_addr

A shorthand for:

  people.every.email_addr

But I like your [*]. Hmm... I maybe able to adjust above to use [:*]
--I'll see.

T.

google for "symbol to_proc". For example:
http://extensions.rubyforge.org/rdoc/classes/Symbol.html

Very nice this one !

···

--
Sylvain Joyeux

nobuyoshi nakada wrote:

Hi,

At Thu, 27 Oct 2005 15:05:41 +0900,
Ron M wrote in [ruby-talk:162876]:

That sure beats C, but wouldn't it be nice if there
were a shorthand for doing so. I think something
like this would be very nice:

   people[*].email_addr

What about:

  module Mappable
    class Mapper
      def initialize(obj)
        @obj = obj
      end
      def method_missing(meth, *args, &block)
        @obj.map {|i| i.send(meth, *args, &block)}
      end
    end
    def self.included(klass)
      super
      aref = klass.instance_method(:)
      klass.module_eval do
        define_method(:) do |*idx|
          if idx.empty?
            Mapper.new(self)
          else
            aref[*idx]
          end
        end
      end
    end
  end

  class Array
    include Mappable
  end

  a = %w[a b c]
  p a.upcase

I don't like it because now does two completely different things:
access and mapping. I'd prefer something like this:

module Enumerable
  # replacement for map
  def mapx(*a,&b)
    raise "Can only have one" if !a.empty? && b
    if a.empty?
      map(&b)
    else
      map {|x| a.inject(x) {|v,m| v.send(m)}}
    end
  end
end

%w{a b c}.mapx :upcase

=> ["A", "B", "C"]

%w{a b c}.mapx {|a| a + "x"}

=> ["ax", "bx", "cx"]

%w{abc bcd cde}.mapx :upcase, :reverse

=> ["CBA", "DCB", "EDC"]

I just chose mapx to get it working fast, ideally the original map method
of Enumerable classes would be changed.

Kind regards

    robert

As always, there's something magical about your code! Unfortunately,
it makes pp blow up for one :wink:

For a similar purpose, I use this - not as slick but simple and
understandable. Doesn't handle the nesting though.

module Enumerable
  def where(&block)
    self.select{|x| x.instance_eval(&block) }
  end
  def project(&block)
    self.map{|x| x.instance_eval(&block) }
  end
end

as in

  depts.where{ name == "Apps" }.project{ people.project{ name } }

Regards,

Sean

···

On 10/27/05, nobuyoshi nakada <nobuyoshi.nakada@ge.com> wrote:

What about:

module Mappable
   class Mapper
     def initialize(obj)
       @obj = obj
     end
     def method_missing(meth, *args, &block)
       @obj.map {|i| i.send(meth, *args, &block)}
     end
   end
   def self.included(klass)
     super
     aref = klass.instance_method(:)
     klass.module_eval do
       define_method(:) do |*idx|
         if idx.empty?
           Mapper.new(self)
         else
           aref[*idx]
         end
       end
     end
   end
end

class Array
   include Mappable
end

a = %w[a b c]
p a.upcase

--
Nobu Nakada

Hi,

At Thu, 27 Oct 2005 21:07:03 +0900,
Trans wrote in [ruby-talk:162907]:

A shorthand for:

  people.every.email_addr

But I like your [*]. Hmm... I maybe able to adjust above to use [:*]

I feel your "every" much cleaner than [*].

···

--
Nobu Nakada

That is neat - I never thought of unifying map and mapx

martin

···

Robert Klemme <bob.news@gmx.net> wrote:

>> %w{a b c}.mapx :upcase
=> ["A", "B", "C"]
>> %w{a b c}.mapx {|a| a + "x"}
=> ["ax", "bx", "cx"]
>> %w{abc bcd cde}.mapx :upcase, :reverse
=> ["CBA", "DCB", "EDC"]

Hi --

···

On Fri, 28 Oct 2005, nobuyoshi nakada wrote:

Hi,

At Thu, 27 Oct 2005 21:07:03 +0900,
Trans wrote in [ruby-talk:162907]:

A shorthand for:

  people.every.email_addr

But I like your [*]. Hmm... I maybe able to adjust above to use [:*]

I feel your "every" much cleaner than [*].

I agree, visually, but I find both of the semantically opaque compared
to people.each {|person| ... } I know that people.every could return
some kind of generator or enumerator, which could then be fed
"email_addr" symbolically... but it seems to conceal rather than
reveal what's going on.

David

--
David A. Black
dblack@wobblini.net

nobuyoshi nakada wrote:

Hi,

At Thu, 27 Oct 2005 21:07:03 +0900,
Trans wrote in [ruby-talk:162907]:

A shorthand for:

people.every.email_addr

But I like your [*]. Hmm... I maybe able to adjust above to use [:*]

I feel your "every" much cleaner than [*].

So...

   people = [ person1, person2, person3 ]
   emails = people.every.email_addr
     => [ 'test@me.com', 'test2@you.com', 'test3@us.com' ]
   # people still is [ person1, person2, person3 ]

   people.every!.nickname
   # people still is now [ 'Joey', 'Zeke', 'Flava Flav' ]

?

Zach

David A. Black wrote:

I know that people.every could return
some kind of generator or enumerator, which could then be fed
"email_addr" symbolically...

module Enumerable
   def every
     enum, obj = self, Object.new
     obj.define_method :method_missing do |name, *args|
       enum.map { |element| element.send(name, *args) }
     end
     return obj
   end
end

ary = ["john", "sylvia", "sarah"]
ary.every.capitalize!

puts ary.join(", ") -> John, Sylvia, Sarah

Cheers,
Daniel

Maybe this can be useful?

···

--
Simon Strandgaard

class Array
  def xmap(*symbols)
    symbols.each do |symbol|
      s = symbol.to_s
      eval "def #{s}(*args);map{|i|i.send(:#{s}, *args)};end"
    end
  end
end

ary = %w(a b c d e)

ary.xmap(:upcase, :gsub)

p ary[0].upcase # "A"
p ary.upcase # ["A", "B", "C", "D", "E"]
p ary.gsub(/[bd]/, 'X') # ["a", "X", "c", "X", "e"]

Daniel Schierbeck wrote:

David A. Black wrote:

I know that people.every could return
some kind of generator or enumerator, which could then be fed
"email_addr" symbolically...

module Enumerable
  def every
    enum, obj = self, Object.new
    obj.define_method :method_missing do |name, *args|
      enum.map { |element| element.send(name, *args) }
    end
    return obj
  end
end

ary = ["john", "sylvia", "sarah"]
ary.every.capitalize!

puts ary.join(", ") -> John, Sylvia, Sarah

Cheers,
Daniel

By the way, you need the Object#define_method if you want it to work:

class Object
   def define_method(*args, &block)
     singleton_class = class << self; self; end
     singleton_class.module_eval do
       define_method(*args, &block)
     end
   end
end

Cheers,
Daniel

I don't like that one, because it provides no visual distinction between
methods that act on the array as a whole and methods that map over it.
Would be a nice place to introduce the -> operator, though :slight_smile:

martin

···

Simon Strandgaard <neoneye@gmail.com> wrote:

ary = %w(a b c d e)

ary.xmap(:upcase, :gsub)

p ary[0].upcase # "A"
p ary.upcase # ["A", "B", "C", "D", "E"]
p ary.gsub(/[bd]/, 'X') # ["a", "X", "c", "X", "e"]

Hi --

Daniel Schierbeck wrote:

David A. Black wrote:

I know that people.every could return
some kind of generator or enumerator, which could then be fed
"email_addr" symbolically...

module Enumerable
  def every
    enum, obj = self, Object.new
    obj.define_method :method_missing do |name, *args|
      enum.map { |element| element.send(name, *args) }

If you must do this, you'd probably want each rather than map there.

    end
    return obj
  end
end

ary = ["john", "sylvia", "sarah"]
ary.every.capitalize!

puts ary.join(", ") -> John, Sylvia, Sarah

Cheers,
Daniel

By the way, you need the Object#define_method if you want it to work:

I definitely *don't* want it to work :slight_smile: I dislike using dot syntax
for non-dot semantics. I've never liked things like:

   hash.where.the.key.equals(10)

even though they can usually be made to work.

I continue to struggle to understand what people find so horrible
about ary.each {|item| .... }, ary.map {|item| ... }, and so forth.
I'll have some more coffee and maybe I'll start to see the light....

David

···

On Fri, 28 Oct 2005, Daniel Schierbeck wrote:

--
David A. Black
dblack@wobblini.net

David A. Black wrote:

Hi --

Daniel Schierbeck wrote:

David A. Black wrote:

I know that people.every could return
some kind of generator or enumerator, which could then be fed
"email_addr" symbolically...

module Enumerable
  def every
    enum, obj = self, Object.new
    obj.define_method :method_missing do |name, *args|
      enum.map { |element| element.send(name, *args) }

If you must do this, you'd probably want each rather than map there.

Nope. I wan't it to return an array of the values returned.

   ["john", "sylvia", "sarah"].every.upcase -> ["JOHN", "SYLVIA", "SARAH"]

That's of course not a very good example. This would probably be better:

   addresses = contacts.every.email_addr

As opposed to

   addresses = contacts.collect { |contact| contact.email_addr }

But I agree that the dot syntax is bad, I was just proving that it could easily be done. This would be better:

   addresses = contacts.every(:email_addr)

And maybe even have a `with_every' method:

   contacts.with_every(:email_addr) do |email_addr|
     puts " - " + email_addr
   end

Which could be implemented this way

   module Enumerable
     def with_every(*args)
       each do |element|
         yield element.send(*args)
       end
     end
   end

Cheers,
Daniel

···

On Fri, 28 Oct 2005, Daniel Schierbeck wrote:

David A. Black wrote:

I continue to struggle to understand what people find so horrible
about ary.each {|item| .... }, ary.map {|item| ... }, and so forth.
I'll have some more coffee and maybe I'll start to see the light....

There's nothing really wrong with them, but they are verbose in simple (and
common) cases:

result = array.map {|element| element.transform }

The only issue is that you're saying "element" twice when you would only say
it once if you were describing the operation to a person.

Groovy's (optional) implicit parameter is the counterpoint:

// Groovy:
result = array.map { it.transform() }

Tangentially, I wonder if the proposed Ruby 2 block syntax makes this
slightly more possible.

# Pseudo-Ruby 2.0:
result = array.map -> { it.transform }

Actually, probably less possible - IIRC the new block style is meant to be
more like the method semantics, and so stricter on parameters.

So, anyway, I don't mind the idea of an implicit parameter, but I realise
it's not going to happen before Ruby 3.

Cheers,
Dave

David A. Black wrote:

I definitely *don't* want it to work :slight_smile: I dislike using dot syntax
for non-dot semantics. I've never liked things like:

   hash.where.the.key.equals(10)

even though they can usually be made to work.

I sort-of agree. It's definitely a semantic we're not used to --using a
method call to give us a "reoriented" version of the same thing --kind
of like having Roles. I suspect this will become a more common paradigm
over time and me may grow acustomed to it, but I'm with you in that I'd
rather have a distinguishing indication of when that's occuring.
Perhaps:

  people:every.email_addr

And maybe that's what Matz already has in mind. And besides, it would
be nice to have it as a language feature becasue current
implementations are not very efficient:

  def every
    Functor.new do |op,*args|
      self.collect{ |a| a.send(op,*args) }
    end
  end

or at best

  def every
    @__every_functor__ ||= Functor.new do |op,*args|
      self.collect{ |a| a.send(op,*args) }
    end
  end

I continue to struggle to understand what people find so horrible
about ary.each {|item| .... }, ary.map {|item| ... }, and so forth.
I'll have some more coffee and maybe I'll start to see the light....

Not horrible, but looking at a language like R with it's elemewise
operations, one kind of wishes we had shorthands techinques as nice.

BTW, I have another version of #every which is even more R-like which
DeMello helped write. But I'm searching for a another name for it. Here
it is:

module Enumerable

  # Returns an elementwise Functor designed to make R-like
  # elementwise operations possible.

···

#
  # [1,2].ew + 3 #=> [4,5]
  # [1,2].ew + [4,5] #=> [5,7]
  # [1,2].ew + [[4,5],3] #=> [[5,7],[4,5]]
  #
  #--
  # Special thanks to Martin DeMello for helping to develop this.
  #++
  def elementwise
    Functor.new do |op,*args|
      a = args.collect do |arg|
        if arg.kind_of?(Enumerable)
          ln = ( arg.length > self.length ? self.length : arg.length )
          self[0...ln].zip(arg[0...ln]).collect{ |a,b| a.send(op,b) }
          #self[0...ln].zip(arg[0...1n]).collect{ |a,b| b ?
a.send(op,b) : nil }
        else
          self.collect{ |a| a.send(op,arg) }
        end
      end
      a.flatten! if args.length == 1
      a
    end
  end

  alias_method :ew, :elementwise

end

Any suggestions?

T.