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

R

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.

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.
 
D

Dave Burt

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
 
N

nobuyoshi nakada

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
 
R

Robert Klemme

nobuyoshi said:
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
 
S

Sean O'Halpin

What about:

module Mappable
class Mapper
def initialize(obj)
@obj =3D obj
end
def method_missing(meth, *args, &block)
@obj.map {|i| i.send(meth, *args, &block)}
end
end
def self.included(klass)
super
aref =3D 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 =3D %w[a b c]
p a[].upcase
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 =3D=3D "Apps" }.project{ people.project{ name } }

Regards,

Sean
 
T

Trans

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.
 
N

nobuyoshi 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 [*].
 
D

David A. Black

Hi --

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
 
Z

zdennis

nobuyoshi said:
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
=> [ '(e-mail address removed)', '(e-mail address removed)', '(e-mail address removed)' ]
# people still is [ person1, person2, person3 ]

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

?

Zach
 
D

Daniel Schierbeck

David said:
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
 
D

Daniel Schierbeck

Daniel said:
David said:
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
 
D

David A. Black

Hi --


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


David
 
D

Daniel Schierbeck

David said:
Hi --




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
 
D

David A. Black

Hi --

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.


David
 
D

Dave Burt

David said:
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
 
D

Daniel Schierbeck

David said:
Hi --

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 }

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.

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.
"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
 
D

David A. Black

Hi --

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() }

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


David
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top