[RCR] join block

[1, 2, 3].join {|x| “

#{x}”}
      =>   "<td>1</td><td>2</td><td>3</td>"

For further info see:
http://www.rubygarden.org/ruby?ArrayBlockJoin

Is this a good/bad idea ?

···


Simon Strandgaard

Simon Strandgaard wrote:

[1, 2, 3].join {|x| “#{x}”}

      =>   "<td>1</td><td>2</td><td>3</td>"

What, you don’t like the utter simplicity of the following? :wink:

[1, 2, 3].map {|x| “#{x}”}.join

I guess the intermediate array is point against this, though.

Somehow, I would expect Array#join to pass two args to the block, like this:

[1,2,3].join {|x,y| x < y ? " < " : " >= " }

 # ==> "1 < 2 < 3"

I know this has been discussed before, but I don’t
know what the conclution were… What is your opinions ?

class Array
alias :oldjoin :join
def join(sep=$,)
return oldjoin(sep) if not block_given?
collect { |y| yield(y) }.oldjoin(sep)
end
end

p [1, 2, 3].join(", ") { |v| “(#{v})” }

“(1), (2), (3)”

p [1, 2, 3].join(" ") { |v| “

#{v}” }

“1 2 3”

puts [1, 2, 3].join { |v| “# #{v}\n” }

1

2

3

p [1, 2, 3].join(" ")

“1 2 3”

···


Simon Strandgaard

Somehow, I would expect Array#join to pass two args to the block,
like this:

[1,2,3].join {|x,y| x < y ? " < " : " >= " }

 # ==> "1 < 2 < 3"

You lost me here. When iterating through the elements, what are x
and y; the current element in the array an the “next” one? What
value does “y” get then on the last element?

I’d think even then, you’d get:

“1 < 22 < 33” # no?

···

Do you Yahoo!?
Yahoo! Calendar - Free online calendar with sync to Outlook™.
http://calendar.yahoo.com

Joel VanderWerf vjoel@PATH.Berkeley.EDU writes:

Somehow, I would expect Array#join to pass two args to the block, like this:

[1,2,3].join {|x,y| x < y ? " < " : " >= " }

 # ==> "1 < 2 < 3"

I can’t figure out how that would happen. I’d think you’d end up
rather with something more like “1 < 2 2 < 3” or something equally
weird. My guess is you’re implicitly assuming some sort of folding
operation going on behind the scenes which is not (to my mind) implied
by ‘join’.

Array#join({block}) seems to me like it should behave as the OP
described: yield every element to {block}, and catenate the results.

-=Eric

···


Come to think of it, there are already a million monkeys on a million
typewriters, and Usenet is NOTHING like Shakespeare.
– Blair Houghton.

Simon Strandgaard wrote:

[1, 2, 3].join {|x| “#{x}”}

      =>   "<td>1</td><td>2</td><td>3</td>"

What, you don’t like the utter simplicity of the following? :wink:

[1, 2, 3].map {|x| “#{x}”}.join

This is simple… but when there is 30 lines between the map/join it
becomes hard to maintain. Array#join which takes a block won’t break
existing code.

I guess the intermediate array is point against this, though.

I think it can be solved without intermediate array… but yes in my
example code i use array.

Somehow, I would expect Array#join to pass two args to the block, like this:

[1,2,3].join {|x,y| x < y ? " < " : " >= " }

 # ==> "1 < 2 < 3"

me don’t understand.

···

On Sat, 07 Jun 2003 06:45:13 +0900, Joel VanderWerf wrote:


Simon Strandgaard

Don’t think the thread reached a definite conclusion, but I’m with Joel
on this - the join block should take in a pair of elements, and return
the substring separating them in the result.

martin

···

Simon Strandgaard 0bz63fz3m1qt3001@sneakemail.com wrote:

I know this has been discussed before, but I don’t
know what the conclution were… What is your opinions ?

Michael Campbell wrote:

Somehow, I would expect Array#join to pass two args to the block,
like this:

[1,2,3].join {|x,y| x < y ? " < " : " >= " }

# ==> "1 < 2 < 3"

You lost me here. When iterating through the elements, what are x
and y; the current element in the array an the “next” one? What
value does “y” get then on the last element?

I’d think even then, you’d get:

“1 < 22 < 33” # no?

I’m thinking the return value of the block should be used in the same
way as the argument to #join would be used, as in:

 [1,2,3].join(" < ")

(This is by analogy with sub(“x”,“y”) and sub(“x”) {“y”}.)

If that’s the case, then the block should only be called on successive
pairs.

But maybe my intuition about how block return values are used is wrong.

What do you do with an odd # of elements?

···

— Martin DeMello martindemello@yahoo.com wrote:

Simon Strandgaard 0bz63fz3m1qt3001@sneakemail.com wrote:

I know this has been discussed before, but I don’t
know what the conclution were… What is your opinions ?

Don’t think the thread reached a definite conclusion, but I’m with
Joel
on this - the join block should take in a pair of elements, and
return
the substring separating them in the result.


Do you Yahoo!?
Yahoo! Calendar - Free online calendar with sync to Outlook™.
http://calendar.yahoo.com

Thinking of what will be most usable in practice:

So far I cannot think of any cases where I need to “join-pairs into a
string”. Can you show some more examples in order to convince me, that this
actualy is usable ? :slight_smile:

I earlier showed you some situations where ‘yield each element and join’
could be useful.

···

On Sun, 08 Jun 2003 22:00:10 +0000, Martin DeMello wrote:

Simon Strandgaard 0bz63fz3m1qt3001@sneakemail.com wrote:

I know this has been discussed before, but I don’t
know what the conclution were… What is your opinions ?

Don’t think the thread reached a definite conclusion, but I’m with Joel
on this - the join block should take in a pair of elements, and return
the substring separating them in the result.


Simon Strandgaard

Maybe, but I, too, would like to use the block form as you suggested.
BTW: I think this came up a couple of weeks ago on ruby-talk.

Regards,
Pit

···

On 7 Jun 2003 at 6:12, Joel VanderWerf wrote:

I’m thinking the return value of the block should be used in the same
way as the argument to #join would be used, as in:

 [1,2,3].join(" < ")

(This is by analogy with sub(“x”,“y”) and sub(“x”) {“y”}.)

If that’s the case, then the block should only be called on successive
pairs.

But maybe my intuition about how block return values are used is wrong.

Simon Strandgaard wrote:

I know this has been discussed before, but I don’t
know what the conclution were… What is your opinions ?

Don’t think the thread reached a definite conclusion, but I’m with Joel
on this - the join block should take in a pair of elements, and return
the substring separating them in the result.

Thinking of what will be most usable in practice:

So far I cannot think of any cases where I need to “join-pairs into a
string”. Can you show some more examples in order to convince me, that this
actualy is usable ? :slight_smile:

I earlier showed you some situations where ‘yield each element and join’
could be useful.

I don’t have any compelling examples. It’s just that if someone said
“Now join takes a block”, this is what I would guess it would do:

class Array
alias old_join join

 def join(str=nil)
   if block_given?
     raise if str # can't think what else to do
     result = ""
     curr = at(0)
     prev = nil
     (length-1).times do |idx|
       prev = curr
       curr = at(idx+1)
       result << prev.to_s
       result << yield(prev, curr).to_str
     end
     result << at(-1).to_s
   else
     old_join(str)
   end
 end

end

puts([1,2,3,4,3,2,1].join do |x,y| x<y ? " < " : " >= " end)

 # ==> 1 < 2 < 3 < 4 >= 3 >= 2 >= 1
 # (No problem with odd-sized arrays!)

puts([1,2,3].join(" still works "))

 # ==> 1 still works 2 still works 3

The block value (in the first case) is used in exactly the same way as
the argument value (in the second case). But that’s just POLS for me,
and IANYM.

I just think what you’re asking for shouldn’t be called #join, but
something else, like #map_then_join. But why stop with join? Why not
#map_then_reverse? Maybe a more general answer would be #map_then_send,
as in:

[1,2,3].map_then_send(:join, ", ") {|x| “<#{x}>”}

==> “<1>, <2>, <3>”

Is this reductio getting close to absurdum yet?

···

On Sun, 08 Jun 2003 22:00:10 +0000, Martin DeMello wrote:

Simon Strandgaard 0bz63fz3m1qt3001@sneakemail.com wrote:

“{” << [1,2,3,“a”,“b”,5,“c”].join {|x,y|
(x.type == y.type) ? “,” : “} {” << “}”

#=> {1,2,3} {a,b} {5} {c}

words.join {|x,y| (x[-1] == ?.) ? “\n” : " "}

Even more useful, once we toss in the idea of a block, might be a
join_with_index:

[1,2,3,4,5,6,7,8,9].join_with_index {|x,y,i|
(i % 3 == 2) ? “\n” : " "}

1 2 3
4 5 6
7 8 9

Though it’d be slightly confusing as to whether i referred to the index
of x or of y.

As for the block working on individual elements, that’s map’s job.

martin

···

Simon Strandgaard 0bz63fz3m1qt3001@sneakemail.com wrote:

So far I cannot think of any cases where I need to “join-pairs into a
string”. Can you show some more examples in order to convince me, that this
actualy is usable ? :slight_smile:

So far I cannot think of any cases where I need to “join-pairs into a
string”. Can you show some more examples in order to convince me, that this
actualy is usable ? :slight_smile:

“{” << [1,2,3,“a”,“b”,5,“c”].join {|x,y|
(x.type == y.type) ? “,” : “} {” << “}”

#=> {1,2,3} {a,b} {5} {c}

words.join {|x,y| (x[-1] == ?.) ? “\n” : " "}

OK… You win, I loose.

As for the block working on individual elements, that’s map’s job.

cjoin = collect-join
pjoin = pair-join

I still think ‘cjoin’ is much more often used in practice than ‘pjoin’.
Everytime I debug or do CGI, I use ‘cjoin’.
I have never been in a situation where ‘pjoin’ could be useful for me.

What is your thoughts on what is most often used ?

···

On Mon, 09 Jun 2003 10:14:00 +0000, Martin DeMello wrote:

Simon Strandgaard 0bz63fz3m1qt3001@sneakemail.com wrote:


Simon Strandgaard

I don’t have any compelling examples. It’s just that if someone said
“Now join takes a block”, this is what I would guess it would do:
[snip]
But that’s just POLS for me, and IANYM.

POLS depends on your preferences and intuition… I guess peoples intuition
differs. ‘collect-join’ seems most intuitive to me :slight_smile:

How about both ideas (cjoin+pjoin) at the same time ?

expand -t2 c.rb
class Array
alias :oldjoin :join
def pair_join(&block)
lhs = first
res = rhs = “”
self[1…-1].each do |rhs|
res += lhs.to_s + block.call(lhs, rhs)
lhs = rhs
end
res + rhs.to_s
end
def collect_join(sep=$, &block)
collect { |y| block.call(y) }.oldjoin(sep)
end
def join(sep=$, &block)
return oldjoin(sep) if not block_given?
return pair_join(&block) if block.arity == 2
collect_join(sep, &block)
end
end

def test_join
puts “A: {”+[1,2,3,“a”,“b”,5,“c”].join { |x, y|
(x.class == y.class) ? “,” : “} {” }+“}”
puts "B: “+[1,2,3].join(”, ") { |v| “(#{v})” }
puts "C: "+[1,2,3].join { |v| “K#{v}O” }
puts “D: “+[1,2,3].join(”+”)
puts "E: "+[1,2,3].join
end

test_join

ruby c.rb
B: (1), (2), (3)
C: K1OK2OK3O
D: 1+2+3
E: 123

···

On Mon, 09 Jun 2003 11:21:35 +0900, Joel VanderWerf wrote:
A: {1,2,3} {a,b} {5} {c}


Simon Strandgaard

I think the interface of Array#join differs too much from the original
Array#join, if the method cannot take a seperator string…
This is the main-problem with your ‘pair-join’ idea.

My suggestion preserves the seperator-string behavier, which i think is
good.

···

On Mon, 09 Jun 2003 11:21:35 +0900, Joel VanderWerf wrote:

 def join(str=nil)
   if block_given?
     raise if str # can't think what else to do
     result = ""
     curr = at(0)
     prev = nil


Simon Strandgaard

puts([1,2,3,4,3,2,1].join do |x,y| x<y ? " < " : " >= " end)

 # ==> 1 < 2 < 3 < 4 >= 3 >= 2 >= 1
 # (No problem with odd-sized arrays!)

puts([1,2,3].join(" still works "))

 # ==> 1 still works 2 still works 3

Nice. I think that the “two elements” version would be the one to pick:
(Assuming you really need this). Otherwise #join with a block would just be a
wrapper around #collect and #join. But I’m not totally sure we need this at
all.

The block value (in the first case) is used in exactly the same way as
the argument value (in the second case). But that’s just POLS for me,
and IANYM.

IANYM == “I am not Yukihiro Matsumoto”?

I just think what you’re asking for shouldn’t be called #join, but
something else, like #map_then_join. But why stop with join? Why not
#map_then_reverse? Maybe a more general answer would be #map_then_send,
as in:

[1,2,3].map_then_send(:join, ", ") {|x| “<#{x}>”}

==> “<1>, <2>, <3>”

Is this reductio getting close to absurdum yet?

Yes. :slight_smile:

We already have a way to chain #collect and #join.

[1,2,3].collect { |x| “<#{x}>” }.join(", ")

=> “<1>, <2>, <3>”

I find this simple, clean and elegant.

However.

It seems having #join take a block might be easier sometimes. And isn’t Ruby
about making programming easy?

OTOH, since adding an option whenever we think it might make things a little
easier could lead to bloat, why don’t we have ‘class-name/extension-name’
directories, so we could do things like this:

require ‘array/joinblock’

And then we start putting Ruby (Or maybe C: Can you change the meaning of
Array#join from a C extension? You probably can, so it wouldn’t have to be in
Ruby) implementations of things like a #join that accepts a block in there,
and if it turns out to be fantastically useful, then move it into the Ruby
interpreter. Or maybe just leave in there and let people load it up whenever
they want it.

And another thing: Why isn’t #join in Enumerable?

Jason Creighton

···

On Mon, 9 Jun 2003 10:21:35 +0900 Joel VanderWerf vjoel@PATH.Berkeley.EDU wrote:

[…]

One option would be to pass the sepString into the block.
No, wait! Don’t run away.

Wholly inspired by your idea:-
(using Enumerable#inject - in later Rubys)

···

“Joel VanderWerf” vjoel@PATH.Berkeley.EDU wrote:

[…] It’s just that if someone said “Now join takes a block”,
this is what I would guess it would do:

class Array
alias old_join join

 def join(str=nil)
   if block_given?
     raise if str # can't think what else to do

class Array
alias :join_orig :join

def join(*args)
if block_given?
ret_ = ‘’
inject(false) do |x, y|
ret_ << (x==false ? nil : yield(x, y, args)).to_s << y.to_s
y
end
ret_
else
join_orig(args[0] || $,)
end
end
end

puts([0,1,2,3].join)
svsep = $, ; $, = ‘…+…’; puts([0,1,2,3].join); $, = svsep
puts([0,1,2,3].join(" still works "))
puts
puts(‘{’ << [1,2,3,“a”,“b”,5,“c”].join(‘,’, ‘} {’) {|x, y, z|
(x.class == y.class) ? z[0] : z[1] } << ‘}’)
puts(‘{’ << [1,2,3,“a”,“b”,5,“c”].join do |x, y|
(x.class == y.class) ? ‘,’ : ‘} {’ end << ‘}’)
puts

“Joel’s Jewel” (as he might prefer it :<D)

puts([1,2,3,4,4,3,7,7,1].join(‘==’, ‘>’, ‘<’) {|x,y,z| " #{ z[x<=>y] } "})

[(v2.0 beta : DfB) - “I knew that ‘spaceship’ was there for a reason”]

#-> 0123
#-> 0…+…1…+…2…+…3
#-> 0 still works 1 still works 2 still works 3
#->
#-> {1,2,3} {a,b} {5} {c}
#-> {1,2,3} {a,b} {5} {c}
#->
#-> 1 < 2 < 3 < 4 == 4 > 3 < 7 == 7 > 1

If it comes to a vote on this, I would like to nominate JVW as my proxy.
Had he not mentioned pairs, this thread might have closed after a
manageable number of comments :slight_smile:

daz
(- Retiring with no hint of an opinion. I’m working on a separate
RCR concerning ‘_with_index’ which I’m hoping will give a whole lot
of support to the ‘*_w_i’ crew - pres. BlackD. who IMHO has been
treated scathingly on this matter . It’ll require max. input
from all quarters so that if YM says YAGNI, we can say YWFWA :!)

cjoin = collect-join
pjoin = pair-join

I still think ‘cjoin’ is much more often used in practice than
‘pjoin’.
Everytime I debug or do CGI, I use ‘cjoin’.
I have never been in a situation where ‘pjoin’ could be useful for
me.

What is your thoughts on what is most often used ?

Maybe it’s because I didn’t have it “natively” anywhere, but I can’t
think of anytime I’ve ever used, nor needed “pjoin”.

Which may only indicate I probably need to expand my horizons some.

···

Do you Yahoo!?
Yahoo! Calendar - Free online calendar with sync to Outlook™.
http://calendar.yahoo.com

I’m not denying the usefulness of a cjoin method, just saying that
cjoin(enum, block) is equivalent to collect(enum, block).join (collect
semantics with an incidental join), whereas pjoin preserves the
semantics of an argument to join, i.e. a string that separates a pair of
consecutive elements.

martin

···

Simon Strandgaard 0bz63fz3m1qt3001@sneakemail.com wrote:

cjoin = collect-join
pjoin = pair-join

I still think ‘cjoin’ is much more often used in practice than ‘pjoin’.
Everytime I debug or do CGI, I use ‘cjoin’.
I have never been in a situation where ‘pjoin’ could be useful for me.

What is your thoughts on what is most often used ?