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.
google for "symbol to_proc". For example:
http://extensions.rubyforge.org/rdoc/classes/Symbol.html
--
vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407
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
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
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
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
That is neat - I never thought of unifying map and mapx
martin
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.
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
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
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
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
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....
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)
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.
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
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
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 :-)
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
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"]
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