Putting some code out to DRY

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

  1. 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
    #1
    1. Advertising

  2. 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,
    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
    Alexey Verkhovsky, Oct 10, 2004
    #2
    1. Advertising

  3. 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
    #3
  4. 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
    #4
  5. "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
    #5
  6. trans.  (T. Onoma)

    Markus Guest

    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
    >


    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
    Markus, Oct 10, 2004
    #6
  7. 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
    #7
  8. trans.  (T. Onoma)

    Jamis Buck Guest

    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
    #8
  9. 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
    #9
  10. 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.

    ---

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

    ---

    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
    #10
  11. trans.  (T. Onoma)

    Jamis Buck Guest

    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
    #11
  12. 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
    #12
  13. |        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.
    trans. (T. Onoma), Oct 13, 2004
    #13
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Saurabh
    Replies:
    6
    Views:
    4,484
    Chris Smith
    May 30, 2004
  2. yoda
    Replies:
    2
    Views:
    271
  3. R. Bernstein

    How do I DRY the following code?

    R. Bernstein, Dec 30, 2008, in forum: Python
    Replies:
    4
    Views:
    211
    R. Bernstein
    Dec 30, 2008
  4. Michel Demazure

    Code to DRY

    Michel Demazure, Oct 30, 2007, in forum: Ruby
    Replies:
    8
    Views:
    91
    Michel Demazure
    Oct 31, 2007
  5. RichardOnRails

    How to DRY code to prompt for parameters

    RichardOnRails, Dec 23, 2010, in forum: Ruby
    Replies:
    7
    Views:
    134
    RichardOnRails
    Dec 29, 2010
Loading...

Share This Page