Join_with

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

On 12/6/06, Martin DeMello [email protected] wrote:

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

join_with is still a little clunky - any better name?

martin

join_mapped maybe?

Max

On 06.12.2006 11:40, Martin DeMello wrote:

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

On Wed, Dec 06, 2006 at 07:40:03PM +0900, Martin DeMello wrote:

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

On 12/6/06, Logan C. [email protected] wrote:

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

I knew someone would say that :slight_smile:

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

#{cell}”}
}

as opposed to

html_table = ary.map {|col|
col.jmap {|cell| “

#{cell}”}.join
}.join("\n")

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

martin

On 12/6/06, Robert K. [email protected] wrote:

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

On Thu, Dec 07, 2006 at 03:12:23AM +0900, Martin DeMello wrote:

take a block, lets add one and pass the deleted item in…)
html_table = ary.map {|col|
col.jmap {|cell| “

#{cell}”}.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| “

#{item}” }

Of course theres everybodies favorite iterator:
array.inject("") { |s, cell| “#{s}

#{cell}\n” }

Martin DeMello wrote:

On 12/6/06, Robert K. [email protected] wrote:

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.

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

On 07.12.2006 12:56, Martin DeMello wrote:

On 12/7/06, Vincent F. [email protected] wrote:

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.

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

robert

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?

Hi –

On Wed, 6 Dec 2006, Martin DeMello wrote:

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

On 12/13/06, Nobuyoshi N. [email protected] wrote:

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?

I think that’s what he’s talking about, yes.

mapconcat is a built-in function in `C source code’.
(mapconcat FUNCTION SEQUENCE SEPARATOR)

Apply FUNCTION to each element of SEQUENCE, and concat the results as
strings.
In between each pair of results, stick in SEPARATOR. Thus, " " as
SEPARATOR results in spaces between the values returned by FUNCTION.
SEQUENCE may be a list, a vector, a bool-vector, or a string.

Of course, in Ruby, FUNCTION == block arg and SEQUENCE == receiver.

/Nick

On 12/7/06, Vincent F. [email protected] wrote:

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

[email protected] wrote:

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

On Thu, 14 Dec 2006 [email protected] wrote:

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

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.

Ara wrote:

On Thu, 14 Dec 2006, [email protected] wrote:

It looks like Ruby >= 1.9 has this:
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.

%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

[email protected] 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

Hi –

On Thu, 14 Dec 2006, Daniel B. wrote:

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

David

On Thu, 14 Dec 2006 [email protected] wrote:

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( [s].map(&:up) )
p( [s].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?

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.

well, that’s hard to argue about. :wink: 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?

:wink:

-a

Hi –

On Thu, 14 Dec 2006, [email protected] wrote:

to a method call on the object.
blocks but currently no arguments - can we always make the meaning the same?
“Munged” is a broad enough term that it probably covers both :slight_smile: 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.

p( [s].map(&:up) )
p( [s].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.

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.

based on extra symbol args. blocks that take multiple args will really
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 &).

self.a = :value

still. we can just call these ‘beauty marks’ can’t we?

I only see one ugly one there :slight_smile: 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