join_with

M

Martin DeMello

While golfing with the hex_ip thread, I realised that map takes no
argument, and join takes no block, leaving a vacuum for a combined
method:

module Enumerable
def join_with(sep, &blk)
map(&blk).join(sep)
end
end

hex_ip.scan(/../).join_with('.') {|i| i.hex}

join_with is still a little clunky - any better name?

martin
 
M

Max Muermann

While golfing with the hex_ip thread, I realised that map takes no
argument, and join takes no block, leaving a vacuum for a combined
method:

module Enumerable
def join_with(sep, &blk)
map(&blk).join(sep)
end
end

hex_ip.scan(/../).join_with('.') {|i| i.hex}

join_with is still a little clunky - any better name?

martin

join_mapped maybe?

Max
 
R

Robert Klemme

While golfing with the hex_ip thread, I realised that map takes no
argument, and join takes no block, leaving a vacuum for a combined
method:

module Enumerable
def join_with(sep, &blk)
map(&blk).join(sep)
end
end

hex_ip.scan(/../).join_with('.') {|i| i.hex}

join_with is still a little clunky - any better name?

Hm. I am not convinced this is a good idea at all. Usually the
recommendation is that methods should do one thing - and one thing only.
Combining map with join seems like a clear violation of that principle
as map and join do extremely different things.

Having said that I'd prefer a more efficient implementation:

module Enumerable
def jw( sep )
first = true

inject("") do |s, x|
if first
first = false
else
s << sep
end

s << yield( x )
end
end
end

Kind regards

robert
 
L

Logan Capaldo

While golfing with the hex_ip thread, I realised that map takes no
argument, and join takes no block, leaving a vacuum for a combined
method:

module Enumerable
def join_with(sep, &blk)
map(&blk).join(sep)
end
end

hex_ip.scan(/../).join_with('.') {|i| i.hex}
hex_ip.scan(/../).map {|i| i.hex}.join('.')
Hey look, the long way is even shorter! Do you just want to conflate
these two because theres room in the interface? That seems like a silly
reason. It would also set a bad precendent. (Well hey, delete doesn't
take a block, lets add one and pass the deleted item in...)
 
M

Martin DeMello

hex_ip.scan(/../).map {|i| i.hex}.join('.')
Hey look, the long way is even shorter! Do you just want to conflate

I knew someone would say that :)
these two because theres room in the interface? That seems like a silly
reason. It would also set a bad precendent. (Well hey, delete doesn't
take a block, lets add one and pass the deleted item in...)

No, more because map-then-join is a very common pattern, and the
complementary argument lists let us combine them neatly.

html_table = ary.jmap("\n") {|col|
col.jmap {|cell| "<td>#{cell}</td>"}
}

as opposed to

html_table = ary.map {|col|
col.jmap {|cell| "<td>#{cell}</td>"}.join
}.join("\n")

The benefit is that the block is the last argument to what is
conceptually a single operation.

martin
 
M

Martin DeMello

Hm. I am not convinced this is a good idea at all. Usually the
recommendation is that methods should do one thing - and one thing only.
Combining map with join seems like a clear violation of that principle
as map and join do extremely different things.

See my reply to Logan - my argument is that often (particularly when
converting data structures to a textual representation) map-then-join
*is* what you want to be doing, and if you're doing a nested series of
them, it's ugly to scatter joins in between the map blocks.
Having said that I'd prefer a more efficient implementation:

Definitely - I just posted the code to explain what I wanted the
method to do. Note that an added advantage is that you needn't create
the temporary array for the map step.

martin
 
L

Logan Capaldo

I knew someone would say that :)


No, more because map-then-join is a very common pattern, and the
complementary argument lists let us combine them neatly.

html_table = ary.jmap("\n") {|col|
col.jmap {|cell| "<td>#{cell}</td>"}
}

as opposed to

html_table = ary.map {|col|
col.jmap {|cell| "<td>#{cell}</td>"}.join
}.join("\n")

The benefit is that the block is the last argument to what is
conceptually a single operation.
I don't see it personally. As far as I am concerned it's two operations.
At least it wasn't just "Hey, theres room for this, why not put it in."

As for the method name, considered just adding a block to join?

array.join("\n") # Normal join
array.join("\n") { |item| "<td>#{item}</td>" }


Of course theres everybodies favorite iterator:
 
V

Vincent Fourmond

Martin said:
Definitely - I just posted the code to explain what I wanted the
method to do. Note that an added advantage is that you needn't create
the temporary array for the map step.

I vote for this function, although the name, join_with, really isn't
telling what it's doing (it doesn't speak to me, unlike most ruby method
names).

Vince
 
M

Martin DeMello

I vote for this function, although the name, join_with, really isn't
telling what it's doing (it doesn't speak to me, unlike most ruby method
names).

I've settled on Enumerable#jmap in my personal code - it fits my
thinking, because I'm focusing on the map, and the join is secondary.

martin
 
R

Robert Klemme

I've settled on Enumerable#jmap in my personal code - it fits my
thinking, because I'm focusing on the map, and the join is secondary.

The I'd spent at least three or four more characters - your current
naming lets me think of some javaism. :)

robert
 
N

Nobuyoshi Nakada

Hi,

At Wed, 6 Dec 2006 19:40:03 +0900,
Martin DeMello wrote in [ruby-talk:228380]:
While golfing with the hex_ip thread, I realised that map takes no
argument, and join takes no block, leaving a vacuum for a combined
method:

Is it what is called "mapconcat" in Emacs Lisp?
 
D

dblack

Hi --

While golfing with the hex_ip thread, I realised that map takes no
argument, and join takes no block, leaving a vacuum for a combined
method:

Your point about map re-raised a question that was in my head
recently.

There was an RCR a while back, rejected by Matz, that asked for:

enum.map:)m)

to be the same as:

enum.map {|e| e.m }

It looks like Ruby >= 1.9 has this:

enum.map(&:m)

which strikes me as the same thing, functionally, but visually noisier
and semantically more obscure.

I'm just wondering what the rationale is for rejecting the simple
version and introducing the less simple one.


David

--
Q. What's a good holiday present for the serious Rails developer?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black)
aka The Ruby book for Rails developers!
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)
 
D

Daniel Berger

Hi --



Your point about map re-raised a question that was in my head
recently.

There was an RCR a while back, rejected by Matz, that asked for:

enum.map:)m)

to be the same as:

enum.map {|e| e.m }

It looks like Ruby >= 1.9 has this:

enum.map(&:m)

which strikes me as the same thing, functionally, but visually noisier
and semantically more obscure.

I'm just wondering what the rationale is for rejecting the simple
version and introducing the less simple one.


David

Good question. One guess is that they wanted a more generic approach,
instead of manually handling specific methods. But that's a guess.

This may have been implemented before, but here's something I've been
toying with:

module Enumerable
# Returns the numeric total of the elements of +enum+.
# Raises an error if any of the elements are non-numeric.
#
def sum
total = 0
each{ |val| total += val }
total
end

# Returns a new array containing the results of running
# +block+ once for every element in the +enum+. Any symbols
# passed as arguments are assumed to be methods, and will be
# called on every element *before* being yielded to the block.
# Non-symbols are assumed to be arguments to those methods.
#
# Examples:
#
# array = ['foo', 'bar']
#
# array.map:)capitalize) => ['Foo', 'Bar']
# array.map:)+, 'x') => ['foox', 'barx']
# array.map:)+, 'y', :upcase) => ['FOOY', 'BARY']
#
def map(*args)
array = [] unless block_given?
hash = {}
key = nil

args.each{ |arg|
if arg.is_a?(Symbol)
key = arg
hash[key] = []
else
hash[key] << arg
end
}

each{ |obj|
hash.each{ |sym, args|
if args.empty?
obj = obj.send(sym)
else
obj = obj.send(sym, *args)
end
}

if block_given?
yield obj
else
array << obj
end
}

return array unless block_given?
end
end

class Array
# Returns a new array containing the results of running
# +block+ once for every element in the +enum+. Any symbols
# passed as arguments are assumed to be methods, and will be
# called on every element *before* being yielded to the block.
# Non-symbols are assumed to be arguments to those methods.
#
# Examples:
#
# array = ['foo', 'bar']
#
# array.map:)capitalize) => ['Foo', 'Bar']
# array.map:)+, 'x') => ['foox', 'barx']
# array.map:)+, 'y', +upcase) => ['FOOY', 'BARY']
#--
# The Array class actually has its own implementation of
# the +map+ method, hence the duplication.
#
def map(*args)
array = [] unless block_given?
hash = {}
key = nil

args.each{ |arg|
if arg.is_a?(Symbol)
key = arg
hash[key] = []
else
hash[key] << arg
end
}

each{ |obj|
hash.each{ |sym, args|
if args.empty?
obj = obj.send(sym)
else
obj = obj.send(sym, *args)
end
}
if block_given?
yield obj
else
array << obj
end
}

return array unless block_given?
end
end
 
A

ara.t.howard

Hi --



Your point about map re-raised a question that was in my head
recently.

There was an RCR a while back, rejected by Matz, that asked for:

enum.map:)m)

to be the same as:

enum.map {|e| e.m }

It looks like Ruby >= 1.9 has this:

enum.map(&:m)

which strikes me as the same thing, functionally, but visually noisier
and semantically more obscure.

I'm just wondering what the rationale is for rejecting the simple
version and introducing the less simple one.


David

i think that makes good sense considering ruby's pattern of not auto-magically
munge aruements: we all agree that it's a good thing that this does not work

"40" + 2

the e.map:)m) is exactly the equivalent of that: it says "by the way, if you
pass me a symbol object i'll auto-magically convert that to a method call on
the object.

now, e.map(&:m), while functionally similar, is completely different. no
magic casting is going on, it's just that symbols have aquired a 'to_proc'
method that returns a block calling that method on any object. this concept
actually has nothing to do with map at all: it's othogonal and totally
generic, for example

%w[ a b c ].each &:display

works as expected. so does this

%w[ c b c ].sort_by &:upcase

so by simply adding Symbol#to_proc a generic mechanism has been created which
can augment potentially any method that takes a block and yields a value.

given that this will work all over the place, it's doubtfull that it will be
more obsure than a special case for map, since people will use the idiom all
over the place.

regards.

-a
 
D

dblack

Hi --

I screwed up my procmailrc briefly and lost a couple of messages. So
I'm answering myself but really answering Ara, whose message I got
from Google groups.
i think that makes good sense considering ruby's pattern of not
auto-magically munge aruements: we all agree that it's a good thing
that this does not work

"40" + 2

the e.map:)m) is exactly the equivalent of that: it says "by the
way, if you pass me a symbol object i'll auto-magically convert that
to a method call on the object.

I don't see a similarity. It's not a conversion to a method; you
couldn't replace the symbol with a method object. It's just a
decision to have the argument have that meaning. It may even be more
circuitous, in a sense, than converting 2 to a string, but I don't
think it's the same at all.
now, e.map(&:m), while functionally similar, is completely different.
no magic casting is going on, it's just that symbols have aquired a
'to_proc' method that returns a block calling that method on any
object. this concept actually has nothing to do with map at all:
it's othogonal and totally generic, for example

%w[ a b c ].each &:display

works as expected. so does this

%w[ c b c ].sort_by &:upcase

so by simply adding Symbol#to_proc a generic mechanism has been
created which
can augment potentially any method that takes a block and yields a
value.

given that this will work all over the place, it's doubtfull that it
will be more obsure than a special case for map, since people will
use the idiom all over the place.

Maybe that's what I'm afraid of. I find it obscure and ugly.


David

--
Q. What's a good holiday present for the serious Rails developer?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black)
aka The Ruby book for Rails developers!
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)
 
D

Daniel Berger

(e-mail address removed) wrote:

given that this will work all over the place, it's doubtfull that it will be
more obsure than a special case for map, since people will use the idiom all
over the place.

I doubt it. My feeling, based on the posts I've seen on the topic over
the last couple of years, is that most people just want it for map and
select.

But, you never know.

Regards,

Dan
 
D

dblack

Hi --

(e-mail address removed) wrote:



I doubt it. My feeling, based on the posts I've seen on the topic over
the last couple of years, is that most people just want it for map and
select.

I hope you're right. I dislike it not only for its line-noise
qualities (I know I can't be *too* squeamish about that), but also
because it seems like a stretch to characterize it as a conversion
from a Symbol to a Proc. Maybe I'll get used to it (or maybe other
people won't :)


David

--
Q. What's a good holiday present for the serious Rails developer?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black)
aka The Ruby book for Rails developers!
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)
 
A

ara.t.howard

I don't see a similarity.

in both cases the arguments types changes the semantics of the method. in one
case a number is numged into a string. in the other a symbol is munged into a
method/proc. in both cases this behaviour is burried in the method and users
of the methods must know that passing certain classes of objects results in
different behaviour. consider this pattern applied to more methods that take
blocks but currently no arguments - can we always make the meaning the same?

It's not a conversion to a method; you couldn't replace the symbol with a
method object.

harp: > cat a.rb
class String
def up(*a) upcase end
end

s = 'forty-two'

p( .map(&:up) )
p( .map(&s.method:)up)) )

harp: > ruby19 a.rb
["FORTY-TWO"]
["FORTY-TWO"]

It's just a decision to have the argument have that meaning.
It may even be more circuitous, in a sense, than converting 2 to a string,
but I don't think it's the same at all.

it's not __just__ having the argument have a meaning, it's having a particular
class of object specify block auto-creation when it was originally nil. the
indirection isn't extensible to other methods, transparent, or orthongonal.
for instance, what does this do?

enum.map:)upcase){|e| e.downcase} # noop

it could throw an error - but why should it? we could simply have rule that
the two blocks, the implied and the actual, are always run in a given order,
say left to right. once we're there, we take the step that multiple args be
given and that they too will be called left to right...

enum.map:)upcase, :downcase){|e| e.upcase} # not noop

now, the impl of map is very useful and yet the code is more complex, next we
can tackle #each, then we can move on to each and every method that takes a
block but takes no args. each one can have similar, or differnt behaviour
based on extra symbol args. blocks that take multiple args will really
complicate the rules. then we can doccument them....

yuk! this is what's known as code acretion and it's a __bad_thing__.

__or__ we can write Symbol#to_proc __once__. doccument it __once__, and every
single method in ruby, stdlibs, and user code can now leverage this
functionality to compactly specified a block of {|elem| elem.send :meth} as
simply &:meth.

you can't really think the former is a better course of action do you?
Maybe that's what I'm afraid of. I find it obscure and ugly.

well, that's hard to argue about. ;-) still, there are alot of ugly syntax
elements in ruby like

class << self

and

@@ugly

and

*list

and

self.a = :value

still. we can just call these 'beauty marks' can't we?

;-)

-a
 
D

dblack

Hi --

in both cases the arguments types changes the semantics of the method. in
one
case a number is numged into a string. in the other a symbol is munged into
a
method/proc. in both cases this behaviour is burried in the method and users
of the methods must know that passing certain classes of objects results in
different behaviour. consider this pattern applied to more methods that take
blocks but currently no arguments - can we always make the meaning the same?

"Munged" is a broad enough term that it probably covers both :) I
don't think they're all that similar at a more granular level. Mind
you, I'm not fully in favor of map:)meth). I'm just not clear on why
the &: thing got in and the argument thing didn't.
It's not a conversion to a method; you couldn't replace the symbol with a
method object.

harp: > cat a.rb
class String
def up(*a) upcase end
end

s = 'forty-two'

p( .map(&:up) )
p( .map(&s.method:)up)) )

harp: > ruby19 a.rb
["FORTY-TWO"]
["FORTY-TWO"]


I was talking about the old RCR, not the new thing. Interesting that
the conversion does indeed seem to be symbol to method to proc, not
symbol to proc.
it's not __just__ having the argument have a meaning, it's having a
particular
class of object specify block auto-creation when it was originally nil. the
indirection isn't extensible to other methods, transparent, or orthongonal.
for instance, what does this do?

enum.map:)upcase){|e| e.downcase} # noop

it could throw an error - but why should it?

Because methods don't take two blocks. The proposal, as I recall, was
that :upcase would trigger the creation of a block -- *the* block.
we could simply have rule that the two blocks, the implied and the
actual, are always run in a given order, say left to right. once
we're there, we take the step that multiple args be given and that
they too will be called left to right...

enum.map:)upcase, :downcase){|e| e.upcase} # not noop

now, the impl of map is very useful and yet the code is more complex, next we
can tackle #each, then we can move on to each and every method that takes a
block but takes no args. each one can have similar, or differnt behaviour
based on extra symbol args. blocks that take multiple args will really
complicate the rules. then we can doccument them....

yuk! this is what's known as code acretion and it's a __bad_thing__.

__or__ we can write Symbol#to_proc __once__. doccument it __once__, and
every
single method in ruby, stdlibs, and user code can now leverage this
functionality to compactly specified a block of {|elem| elem.send :meth} as
simply &:meth.

you can't really think the former is a better course of action do you?

I don't follow the reasoning here. Of course you could add arbitrary
numbers of arguments and blocks to anything and make it unwieldy.
That wasn't what was proposed, though; it was just giving iterators
that don't take arguments an argument, as a concise way to create a
block (like &: but without the &).
well, that's hard to argue about. ;-) still, there are alot of ugly syntax
elements in ruby like

class << self

and

@@ugly

and

*list

and

self.a = :value

still. we can just call these 'beauty marks' can't we?

I only see one ugly one there :) In any case, I don't think the
answer is to shrug and say, "Well, there's already a bit of line noise
here and there, so let's throw in the towel and go all the way." So
much of what's cool in Ruby is cool because of the individual
attention to things -- the hand-crafting, so to speak. This one just
seems like not one of the big successes, stylistically or
semantically, to me.


David

--
Q. What's a good holiday present for the serious Rails developer?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black)
aka The Ruby book for Rails developers!
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)
 
M

Martin DeMello

At Wed, 6 Dec 2006 19:40:03 +0900,
Martin DeMello wrote in [ruby-talk:228380]:
While golfing with the hex_ip thread, I realised that map takes no
argument, and join takes no block, leaving a vacuum for a combined
method:

Is it what is called "mapconcat" in Emacs Lisp?

Yes, just looked it up and it's what I'm talking about. Might adopt
the name for consistency's sake, though join_map has grown on me.

martin
 

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

No members online now.

Forum statistics

Threads
474,220
Messages
2,571,128
Members
47,744
Latest member
FrederickM

Latest Threads

Top