Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

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

13 views
Skip to first unread message

Ron M

unread,
Oct 27, 2005, 2:05:41 AM10/27/05
to

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.


Joel VanderWerf

unread,
Oct 27, 2005, 2:25:12 AM10/27/05
to
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


Dave Burt

unread,
Oct 27, 2005, 2:33:08 AM10/27/05
to
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


Sylvain Joyeux

unread,
Oct 27, 2005, 2:47:11 AM10/27/05
to
> google for "symbol to_proc". For example:
> http://extensions.rubyforge.org/rdoc/classes/Symbol.html
Very nice this one !
--
Sylvain Joyeux


nobuyoshi nakada

unread,
Oct 27, 2005, 4:46:17 AM10/27/05
to
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


Robert Klemme

unread,
Oct 27, 2005, 5:04:43 AM10/27/05
to

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

Sean O'Halpin

unread,
Oct 27, 2005, 6:32:24 AM10/27/05
to
As always, there's something magical about your code! Unfortunately,
it makes pp blow up for one ;)

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


Martin DeMello

unread,
Oct 27, 2005, 6:49:32 AM10/27/05
to
Robert Klemme <bob....@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"]

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

martin

Trans

unread,
Oct 27, 2005, 8:02:15 AM10/27/05
to

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.

nobuyoshi nakada

unread,
Oct 27, 2005, 10:45:11 PM10/27/05
to
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


David A. Black

unread,
Oct 28, 2005, 1:11:06 AM10/28/05
to
Hi --

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
dbl...@wobblini.net


zdennis

unread,
Oct 28, 2005, 1:45:46 AM10/28/05
to

So...

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

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

?

Zach

Daniel Schierbeck

unread,
Oct 28, 2005, 8:11:08 AM10/28/05
to
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

Daniel Schierbeck

unread,
Oct 28, 2005, 8:45:22 AM10/28/05
to

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

David A. Black

unread,
Oct 28, 2005, 9:45:40 AM10/28/05
to
Hi --

On Fri, 28 Oct 2005, Daniel Schierbeck wrote:

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

Daniel Schierbeck

unread,
Oct 28, 2005, 10:06:33 AM10/28/05
to
David A. Black wrote:
> Hi --
>
> On Fri, 28 Oct 2005, Daniel Schierbeck wrote:
>
>> 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)

David A. Black

unread,
Oct 28, 2005, 10:16:44 AM10/28/05
to
Hi --

On Fri, 28 Oct 2005, Daniel Schierbeck wrote:

> David A. Black wrote:
>> Hi --
>>
>> On Fri, 28 Oct 2005, Daniel Schierbeck wrote:
>>
>>> 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.

Oh right, never mind.

> ["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 }

I strongly prefer the latter. You've gone out of your way to make it
long and wordy :-)

addresses = contacts.map {|c| c.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)

There was an RCR once for enum.map(:method) {|x| ... } but it was
rejected.

> And maybe even have a `with_every' method:
>
> contacts.with_every(:email_addr) do |email_addr|
> puts " - " + email_addr
> end

"with_every" feels like the wrong word, though. It suggests that
they're all being used at the same time. Maybe:

contacts.each_send(:email_addr)

or something.

Dave Burt

unread,
Oct 28, 2005, 10:42:43 AM10/28/05
to
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


Daniel Schierbeck

unread,
Oct 28, 2005, 11:41:36 AM10/28/05
to

That's of course a matter of taste, but I don't like single-letter
variables. Abbreviations of long words can do, but the variable name
should reflect the object it is referencing. Furthermore, I often use
`collect' instead of `map' simply because that's what I'm doing:
collecting email-addresses from a list of contacts.


>> 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)
>
>
> There was an RCR once for enum.map(:method) {|x| ... } but it was
> rejected.
>

I don't see why a method such as `map' should work that way - it isn't
implied by the method name.

>> And maybe even have a `with_every' method:
>>
>> contacts.with_every(:email_addr) do |email_addr|
>> puts " - " + email_addr
>> end
>
>
> "with_every" feels like the wrong word, though. It suggests that
> they're all being used at the same time. Maybe:
>
> contacts.each_send(:email_addr)
>
> or something.
>
>

Maybe. `with_each' would also do. But still I think it is inconsistent
to have `each' yield each object in a collection and `each_send` yield
the result of calling a method on those objects. I'd rather we have
`every' handle the latter.

# calls `method' on each object in `collection'
# and returns the return values of those calls
# in an array
collection.every :method

# calls `method' on each object in `collection'
# and yields the return values
collection.with_every :method do |result|; end

They could of course both be contained in a single method, which both
returned an array and yielded the return values.

I'm not suggesting that we put this in the Ruby Core, but I think it's a
great library method.


Cheers,
Daniel

David A. Black

unread,
Oct 28, 2005, 11:44:29 AM10/28/05
to
Hi --

On Fri, 28 Oct 2005, Dave Burt 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.

I probably would say "element" at least twice in describing it to a
person, I think. "Go through the array one element at a time, call
the "transform" method on the current element, and save the result to
an accumulator array" or something like that.

> Groovy's (optional) implicit parameter is the counterpoint:
>
> // Groovy:
> result = array.map { it.transform() }

<shudder/> :-)

> Tangentially, I wonder if the proposed Ruby 2 block syntax makes this
> slightly more possible.
>
> # Pseudo-Ruby 2.0:
> result = array.map -> { it.transform }

<ugh/> :-) I hope that's more pseudo than Ruby 2 :-)

Trans

unread,
Oct 28, 2005, 11:48:27 AM10/28/05
to

David A. Black wrote:
> I definitely *don't* want it to work :-) 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.

Simon Strandgaard

unread,
Oct 28, 2005, 1:17:54 PM10/28/05
to
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"]


Martin DeMello

unread,
Oct 30, 2005, 5:41:39 AM10/30/05
to
Simon Strandgaard <neo...@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"]

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 :)

martin

0 new messages