# Putting some code out to DRY

Discussion in 'Ruby' started by trans. (T. Onoma), Oct 10, 2004.

1. ### trans. (T. Onoma)Guest

Notice the three lines of duplication. Any ideas on DRYing this up?

def each
seed = @start
if @step
@step.times do
yield(seed)
@skip.times { seed = seed.succ }
break if @halt.call(seed) if @halt
end
else
loop do
yield(seed)
@skip.times { seed = seed.succ }
break if @halt.call(seed) if @halt
end
end
end

Thanks,
T.

[CO2D] or [WET] ?

trans. (T. Onoma), Oct 10, 2004

2. ### Alexey VerkhovskyGuest

On Sun, 2004-10-10 at 22:25, trans. (T. Onoma) wrote:
> Notice the three lines of duplication. Any ideas on DRYing this up?

I would extract them into a method.

Another alternative, at a price of checking @step on every iteration,

# UNTESTED

def each
seed = @start
counter = 0
loop do
break if @step and (counter += 1) > step
yield(seed)
@skip.times { seed = seed.succ }
break if @halt.call(seed) if @halt
end
end

Alexey Verkhovsky, Oct 10, 2004

3. ### Florian GrossGuest

trans. (T. Onoma) wrote:

> Notice the three lines of duplication. Any ideas on DRYing this up?
>
> def each
> seed = @start

@step ||= 1.0 / 0.0 # Infinity
@step.times do
yield(seed)
@skip.times { seed = seed.succ }
break if @halt and @halt.call(seed)
> end
> end

And maybe this: (in case @start is numeric)

def each
(@start .. (1.0 / 0.0)).step(@skip) do |seed|
yield(seed)
break if @halt and @halt.call(seed)
end
end

> Thanks,
> T.

Regards,
Florian Gross

Florian Gross, Oct 10, 2004
4. ### Randy W. SimsGuest

loop do
break if @step && (@step -= 1) < 0
yield(seed)
@skip.times { seed = seed.succ }
break if @halt.call(seed) if @halt
end

Randy W. Sims, Oct 10, 2004
5. ### Mikael BrockmanGuest

"trans. (T. Onoma)" <> writes:

> Notice the three lines of duplication. Any ideas on DRYing this up?
>
> def each
> seed = @start
> if @step
> @step.times do
> yield(seed)
> @skip.times { seed = seed.succ }
> break if @halt.call(seed) if @halt
> end
> else
> loop do
> yield(seed)
> @skip.times { seed = seed.succ }
> break if @halt.call(seed) if @halt
> end
> end
> end

You could let @step default to \$infinity, where \$infinity is something
like this:

| \$infinity = Object.new
| class << \$infinity
| def times; loop { yield }; end
| # other infinity methods omitted because YAGNI
| end

That would let you define #each as simply

| def each
| seed = @start
| @step.times do
| yield(seed)
| @skip.times { seed = seed.succ }
| break if @halt.call(seed) if @halt
| end
| end

Mikael Brockman, Oct 10, 2004
6. ### MarkusGuest

On Sun, 2004-10-10 at 12:25, trans. (T. Onoma) wrote:
> Notice the three lines of duplication. Any ideas on DRYing this up?
>
> def each
> seed = @start
> if @step
> @step.times do
> yield(seed)
> @skip.times { seed = seed.succ }
> break if @halt.call(seed) if @halt
> end
> else
> loop do
> yield(seed)
> @skip.times { seed = seed.succ }
> break if @halt.call(seed) if @halt
> end
> end
> end
>

Forever = Object.new
def Forever.times
loop {yield}
end

def each
seed = @start
(@step || Forever).times do
yield(seed)
@skip.times { seed = seed.succ }
break if @halt.call(seed) if @halt
end
end

-- Markus

Markus, Oct 10, 2004
7. ### Mauricio FernándezGuest

On Mon, Oct 11, 2004 at 04:25:30AM +0900, trans. (T. Onoma) wrote:
>
> Notice the three lines of duplication. Any ideas on DRYing this up?
>
> def each
> seed = @start

Am I the only one who sometimes does things resembling
whatever = lambda do
yield(seed)
@skip.times { seed = seed.succ }
break if @halt.call(seed) if @halt
end
and then whatever[] ??

> if @step
> @step.times do
> yield(seed)
> @skip.times { seed = seed.succ }
> break if @halt.call(seed) if @halt
> end
> else
> loop do
> yield(seed)
> @skip.times { seed = seed.succ }
> break if @halt.call(seed) if @halt
> end
> end
> end
>
> Thanks,
> T.
>
>
> [CO2D] or [WET] ?
>
>

--
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

Mauricio Fernández, Oct 10, 2004
8. ### Jamis BuckGuest

Mauricio Fernández wrote:
> On Mon, Oct 11, 2004 at 04:25:30AM +0900, trans. (T. Onoma) wrote:
>
>>Notice the three lines of duplication. Any ideas on DRYing this up?
>>
>> def each
>> seed = @start

>
>
> Am I the only one who sometimes does things resembling
> whatever = lambda do
> yield(seed)
> @skip.times { seed = seed.succ }
> break if @halt.call(seed) if @halt
> end
> and then whatever[] ??

Glad you mentioned this, batsman. I was just about to. Let me fill in
the blank at the end of your example, though, since I'm the type of guy
that just can't stand a song only partially sung...

@step ? @step.times(&whatever) : loop(&whatever)

- Jamis

>
>
>
>> if @step
>> @step.times do
>> yield(seed)
>> @skip.times { seed = seed.succ }
>> break if @halt.call(seed) if @halt
>> end
>> else
>> loop do
>> yield(seed)
>> @skip.times { seed = seed.succ }
>> break if @halt.call(seed) if @halt
>> end
>> end
>> end
>>
>>Thanks,
>>T.
>>
>>
>>[CO2D] or [WET] ?
>>
>>

>
>

--
Jamis Buck

http://www.jamisbuck.org/jamis

Jamis Buck, Oct 10, 2004
9. ### Mikael BrockmanGuest

Mauricio Fernández <> writes:

> On Mon, Oct 11, 2004 at 04:25:30AM +0900, trans. (T. Onoma) wrote:
> >
> > Notice the three lines of duplication. Any ideas on DRYing this up?
> >
> > def each
> > seed = @start

>
> Am I the only one who sometimes does things resembling
> whatever = lambda do
> yield(seed)
> @skip.times { seed = seed.succ }
> break if @halt.call(seed) if @halt
> end
> and then whatever[] ??

Nope. I do that all the time. I also often do stuff like

class Foo
define_thing = lambda do |id, y|
define_method x do |z|
do_some_stuff z, y
do_some_other_stuff z, y
end
end

define_thing.call :foo
define_thing.call :bar
define_thing.call :baz
end

Mikael Brockman, Oct 10, 2004
10. ### trans. (T. Onoma)Guest

Multiple responses below:

On Sunday 10 October 2004 03:59 pm, Florian Gross wrote:
| > def each
| > seed = @start
|
| @step ||= 1.0 / 0.0 # Infinity
| @step.times do
| yield(seed)
| @skip.times { seed = seed.succ }
| break if @halt and @halt.call(seed)
|
| > end
| > end

This one blew-up on 1.8.2, no Float#times

| And maybe this: (in case @start is numeric)
|
| def each
| (@start .. (1.0 / 0.0)).step(@skip) do |seed|
| yield(seed)
| break if @halt and @halt.call(seed)
| end
| end

Oh the irony here! It sort of uses what I'm rewriting

Thanks.

---

On Sunday 10 October 2004 04:03 pm, Mikael Brockman wrote:
| You could let @step default to \$infinity, where \$infinity is something
|
| like this:
| | \$infinity = Object.new
| | class << \$infinity
| | def times; loop { yield }; end
| | # other infinity methods omitted because YAGNI
| | end
|
| That would let you define #each as simply
|
| | def each
| | seed = @start
| | @step.times do
| | yield(seed)
| | @skip.times { seed = seed.succ }
| | break if @halt.call(seed) if @halt
| | end
| | end

and

On Sunday 10 October 2004 05:36 pm, Markus wrote:
| > end
|
|
| Forever = Object.new
| def Forever.times
| loop {yield}
| end
|
| def each
| seed = @start
| (@step || Forever).times do
| yield(seed)
| @skip.times { seed = seed.succ }
| break if @halt.call(seed) if @halt
| end
| end

These two are similar. And I'm sure I'd go this way if Infinite/Forever were a
standard class, and yet I may go this route anyway.

Hmm... may be an interesting place to use one of those method procs:

forever = proctimes){|&blk| blk.call}

(1.9 only I think) Just a thought.

Thanks.

---

On Sunday 10 October 2004 03:51 pm, Alexey Verkhovsky wrote:
| On Sun, 2004-10-10 at 22:25, trans. (T. Onoma) wrote:
| > Notice the three lines of duplication. Any ideas on DRYing this up?
|
| I would extract them into a method.
|
| Another alternative, at a price of checking @step on every iteration,
|
| # UNTESTED
|
| def each
| seed = @start
| counter = 0
| loop do
| break if @step and (counter += 1) > step
| yield(seed)
| @skip.times { seed = seed.succ }
| break if @halt.call(seed) if @halt
| end
| end

and

On Sunday 10 October 2004 03:59 pm, Randy W. Sims wrote:
| loop do
| break if @step && (@step -= 1) < 0
| yield(seed)
| @skip.times { seed = seed.succ }
| break if @halt.call(seed) if @halt
| end

Extra break. I bet this would be the thing to do if using a lower level
language. Using Ruby though, I'm guessing other means are more efficient. You
concur?

Thanks.

---

On Sunday 10 October 2004 05:55 pm, Mauricio FernÃ¡ndez wrote:
| On Mon, Oct 11, 2004 at 04:25:30AM +0900, trans. (T. Onoma) wrote:
| > Notice the three lines of duplication. Any ideas on DRYing this up?
| >
| > def each
| > seed = @start
|
| Am I the only one who sometimes does things resembling
| whatever = lambda do
| yield(seed)
| @skip.times { seed = seed.succ }
| break if @halt.call(seed) if @halt
| end
| and then whatever[] ??
|

and

On Sunday 10 October 2004 06:14 pm, Jamis Buck wrote:
| Glad you mentioned this, batsman. I was just about to. Let me fill in
| the blank at the end of your example, though, since I'm the type of guy
| that just can't stand a song only partially sung...
|
| @step ? @step.times(&whatever) : loop(&whatever)

This was my thought too. But when I did it that way I thought maybe I was
loosing a little efficiency assigning and calling the proc, and wondered if
there were any better ways, but maybe not.

---

Thanks everyone. Let you know what I settle on.

T.

trans. (T. Onoma), Oct 11, 2004
11. ### Jamis BuckGuest

trans. (T. Onoma) wrote:

> On Sunday 10 October 2004 05:55 pm, Mauricio FernÃ¡ndez wrote:
> | On Mon, Oct 11, 2004 at 04:25:30AM +0900, trans. (T. Onoma) wrote:
> | > Notice the three lines of duplication. Any ideas on DRYing this up?
> | >
> | > def each
> | > seed = @start
> |
> | Am I the only one who sometimes does things resembling
> | whatever = lambda do
> | yield(seed)
> | @skip.times { seed = seed.succ }
> | break if @halt.call(seed) if @halt
> | end
> | and then whatever[] ??
> |
>
> and
>
> On Sunday 10 October 2004 06:14 pm, Jamis Buck wrote:
> | Glad you mentioned this, batsman. I was just about to. Let me fill in
> | the blank at the end of your example, though, since I'm the type of guy
> | that just can't stand a song only partially sung...
> |
> | @step ? @step.times(&whatever) : loop(&whatever)
>
> This was my thought too. But when I did it that way I thought maybe I was
> loosing a little efficiency assigning and calling the proc, and wondered if
> there were any better ways, but maybe not.

Try some benchmarks. I've found I'm nearly always wrong in what I assume
to efficient vs. inefficient in Ruby, and benchmarks are usually trivial
to write:

require 'benchmark'

class Test
def initialize( start, step, skip, &halt )
@start = start
@step = step
@skip = skip
@halt = halt
end

def each1
seed = @start
if @step
@step.times do
yield(seed)
@skip.times { seed = seed.succ }
break if @halt.call(seed) if @halt
end
else
loop do
yield(seed)
@skip.times { seed = seed.succ }
break if @halt.call(seed) if @halt
end
end
end

def each2
seed = @start
whatever = lambda do
yield(seed)
@skip.times { seed = seed.succ }
return if @halt.call(seed) if @halt
end
@step ? @step.times(&whatever) : loop(&whatever)
end
end

Benchmark.bm do |x|
with_step = Test.new( 1, 100000, 1 )
without_step = Test.new( 1, nil, 1 ) { |s| s > 100000 }

x.report { with_step.each1 { |s| } }
x.report { without_step.each1 { |s| } }
x.report { with_step.each2 { |s| } }
x.report { without_step.each2 { |s| } }
end

Turns out, the two approaches are virtually identical in execution times:

user system total real
0.200000 0.000000 0.200000 ( 0.233535)
0.370000 0.000000 0.370000 ( 0.428697)
0.200000 0.000000 0.200000 ( 0.239925)
0.370000 0.000000 0.370000 ( 0.405779)

- Jamis

--
Jamis Buck

http://www.jamisbuck.org/jamis

Jamis Buck, Oct 11, 2004
12. ### trans. (T. Onoma)Guest

On Sunday 10 October 2004 09:58 pm, Jamis Buck wrote:| Turns out, the two approaches are virtually identical in execution times:|| Â  Â  Â  Â user Â  Â  system Â  Â  Â total Â  Â  Â  Â real| Â  Â 0.200000 Â  0.000000 Â  0.200000 ( Â 0.233535)| Â  Â 0.370000 Â  0.000000 Â  0.370000 ( Â 0.428697)| Â  Â 0.200000 Â  0.000000 Â  0.200000 ( Â 0.239925)| Â  Â 0.370000 Â  0.000000 Â  0.370000 ( Â 0.405779)
I'll be! Thanks, Jamis. I'll give the others a try.
T.

trans. (T. Onoma), Oct 11, 2004
13. ### trans. (T. Onoma)Guest

| Â  Â  Â  Â seed = @start
| Â  Â  Â  Â whatever = lambda do
| Â  Â  Â  Â  Â yield(seed)
| Â  Â  Â  Â  Â @skip.times { seed = seed.succ }
| Â  Â  Â  Â  Â return if @halt.call(seed) if @halt
| Â  Â  Â  Â end
| Â  Â  Â  Â @step ? @step.times(&whatever) : loop(&whatever)
| Â  Â  Â end

FYI: I ended up using this lambda approach after all. Many thanks to those who
suggested it.

I occurs to me that this also has to do with 1st class methods. An (as of yet)
untested possibility:

( @step ? @step.methodtimes) : methodloop) ).call proc do
Â  Â  Â yield(seed)
Â  Â  Â @skip.times { seed = seed.succ }
Â  Â  Â return if @halt.call(seed) if @halt
end

T.

trans. (T. Onoma), Oct 13, 2004