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

join_mapped maybe?

Max

···

On 12/6/06, Martin DeMello <martindemello@gmail.com> 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:

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

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 06.12.2006 11:40, 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:

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?

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

···

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

join_with is still a little clunky - any better name?

martin

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?

···

--
Nobu Nakada

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

···

On Wed, 6 Dec 2006, Martin DeMello wrote:

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

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

···

On 12/6/06, Logan Capaldo <logancapaldo@gmail.com> 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 12/6/06, Robert Klemme <shortcutter@googlemail.com> 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/13/06, Nobuyoshi Nakada <nobu@ruby-lang.org> wrote:

dblack@wobblini.net wrote:

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

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.

···

On Wed, 6 Dec 2006, Martin DeMello wrote:

   #
   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

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

···

On Thu, 14 Dec 2006 dblack@wobblini.net wrote:

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

--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama

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:

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

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

···

On Thu, 14 Dec 2006, dblack@wobblini.net wrote:

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

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

···

On 12/13/06, Nobuyoshi Nakada <nobu@ruby-lang.org> wrote:

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?

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.

The 1.9 version is nicely orthogonal, though - it hasn't added
anything to #map, since the Symbol#to_proc method is called eagerly,
*before* passing the resultant proc to #map. And #map has always been
able to take a proc:

(1..10).map &lambda {|i| i*2}

=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

So this hasn't really changed anything, it's just added a convenience
method to symbol. There's actually considerably less magic going on
than, say, #join's automatically converting everything to strings, and
the complexity is a result of that lack of magic.

martin

···

On 12/13/06, dblack@wobblini.net <dblack@wobblini.net> wrote:

Hi,

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.

(a) noisier form works not only for map, but also for every method
    that take a block.

(b) if we wanted to add same functionality for every iterator method,
    we have to modify them all. besides that, some iterator methods
    already take arguments that make less noisy form difficult.

(c) ampersand would serve as visual clue, which tells you that symbol
    works as some kind of block. it is difficult to tell what :m
    means in less noisy form. that could be a method name that map
    based on instead of each, or some other thing.

Choose whichever you want.

              matz.

···

In message "Re: map taking an argument (was: Re: join_with)" on Thu, 14 Dec 2006 01:58:15 +0900, dblack@wobblini.net writes:

>>
>> 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| "<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:
array.inject("") { |s, cell| "#{s}<td>#{cell}</td>\n" }

···

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

On 12/6/06, Logan Capaldo <logancapaldo@gmail.com> wrote:

martin

Martin DeMello wrote:

···

On 12/6/06, Robert Klemme <shortcutter@googlemail.com> 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

--
Vincent Fourmond, PhD student
http://vincent.fourmond.neuf.fr/

<snip>

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

···

ara.t.howard@noaa.gov wrote:

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.

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"]

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

···

On Thu, 14 Dec 2006 dblack@wobblini.net wrote:
--
if you find yourself slandering anybody, first imagine that your mouth is
filled with excrement. it will break you of the habit quickly enough. - the
dalai lama

Hi --

···

On Thu, 14 Dec 2006, Martin DeMello wrote:

On 12/13/06, dblack@wobblini.net <dblack@wobblini.net> wrote:

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.

The 1.9 version is nicely orthogonal, though - it hasn't added
anything to #map, since the Symbol#to_proc method is called eagerly,
*before* passing the resultant proc to #map. And #map has always been
able to take a proc:

(1..10).map &lambda {|i| i*2}

=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

So this hasn't really changed anything, it's just added a convenience
method to symbol. There's actually considerably less magic going on
than, say, #join's automatically converting everything to strings, and
the complexity is a result of that lack of magic.

Interesting -- I perceive it as quite magic, maybe because no matter
how long I stare at it, &:m doesn't say "block based on iteration with
method named 'm'" to me.

Mind you, there's *still* room for doing something with the argument
to map :slight_smile:

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)