Putting some code out to DRY

T

trans. (T. Onoma)

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] ?
 
A

Alexey Verkhovsky

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,
and much less readable than your original:

# 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
 
F

Florian Gross

trans. (T. Onoma) said:
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)

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
 
R

Randy W. Sims

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

Mikael Brockman

trans. (T. Onoma) said:
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
 
M

Markus

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

How about:

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
 
M

Mauricio Fernández

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] ?
 
J

Jamis Buck

Mauricio said:
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] ?
 
M

Mikael Brockman

Mauricio Fernández said:
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
 
T

trans. (T. Onoma)

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
|
| How about:
|
| 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 = proc:)times){|&blk| blk.call}

(1.9 only I think) Just a thought.

Thanks.

---

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,
| and much less readable than your original:
|
| # 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.

---

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

Jamis Buck

trans. (T. Onoma) said:
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
 
T

trans. (T. Onoma)

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

trans. (T. Onoma)

|        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.method:)times) : method:)loop) ).call proc do
     yield(seed)
     @skip.times { seed = seed.succ }
     return if @halt.call(seed) if @halt
end

T.
 

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

Staff online

Members online

Forum statistics

Threads
473,769
Messages
2,569,577
Members
45,052
Latest member
LucyCarper

Latest Threads

Top