Here are three Ruby functions…

Here are three Ruby functions. Each solves this problem: “You are given a starting and ending date and an increment in days. Produce all incremental dates that don’t include the starting date but may include the ending date. More formally: produce a list of all the dates such that for some n >= 1, date = starting_date + (increment * n) && date < = ending_date.

Solution 1:

Solution 2:

Solution 3 depends on a lazily function that produces an unbounded list from a starting element and a next-element function. Here’s a use of lazily:

As a lazy sequence, integers both (1) doesn’t do the work of calculating the ith element unless a client of integers asks for it, and also (2) doesn’t waste effort calculationg any intermediate values more than once.

Solution 3:

The third solution seems “intuitively” better to me, but I’m having difficulty explaining why.

The first solution fails on three aesthetic grounds:

  • It lacks decomposability. There’s no piece that can be ripped out and used in isolation. (For example, the body of the loop both creates a new element and updates an intermediate value.)

  • It lacks flow. It’s pleasing when you can view a computation as flowing a data structure through a series of functions, each of which changes its “shape” to convert a lump of coal into a diamond.

  • It has wasted motion: it puts an element at the front of the array, then throws it away. (Note: you can eliminate that by having current start out as exclusive+increment but that code duplicates the later +=increment. Arguably, that duplicated increment-date action is wasted (programmer) motion, in the sense that the same action is done twice. (Or: don’t repeat yourself / Eliminate duplication.))

The second solution has flow of values through functions, but it wastes a lot of motion. A bunch of dates are created, only to be thrown away in the next step of the computation. Also, in some way I cannot clearly express, it seems wrong to stick the inclusive_end between the exclusive_start and the increment, given that the latter two are what was originally presented to the user and the inclusive_end is a user choice. (Therefore shouldn’t the exclusive_start and increment be more visually bound together than this solution does?)

The third solution …

  • … is decomposable: the sequence of dates is distinct from the decision about which subset to use. (You could, for example, pass in the whole lazy sequence instead of a exclusive_start/increment pair, something that couldn’t be done with the other solutions.)

  • … eliminates wasted work, in that only the dates that are required are generated. (Well, it does store away a first date — excluded_start — that is then dropped. But it doesn’t create an excess new date.)

  • … has the same feel of a data structure flowing through functions that #2 has.

So: #3 seems best to me, but the advantages over the other two seem unconvincing (especially given that programmers of my generation are likely to see closure-evaluation-requiring-heap-allocation-because-of-who-knows-what-bound-variables as scarily expensive).

Have you better arguments? Can you refute my arguments?

I’m trying to show the virtues of a lazy functional style. Perhaps this is a bad example? [It’s a real one, though, where I really do prefer the third solution.]

5 Responses to “Here are three Ruby functions…”

  1. bl0rq Says:

    My C# version, which i find much more “readable” (subjective, i know) than the 3rd option, but still is lazy and composed etc: (Not tested well or properly input tested etc. Do not use in production code.)

    private static IEnumerable Range ( DateTime start, DateTime end, int offsetDays = 1 )
    {
    int count = (int)( ( end - start ).TotalDays / offsetDays );
    return Enumerable.Range ( 1, count ).Select ( n => start.AddDays ( n * offsetDays ) );
    }

  2. cwallenpoole Says:

    What about Python:


    #built-in
    def dates(start,end,offset):
    return range(start+offset, end, offset)

    # if you needed to do this by yourself
    def dates(start, end, offset):
    while True:
    start += offset
    if start > end
    break
    yield start

  3. benfulton Says:

    I like the yield statement too. In C# I would do


    private static IEnumerable Range ( DateTime start, DateTime end, int offsetDays = 1 )
    {
    var current = start;
    while (current

    (also not tested :))

  4. benfulton Says:

    That got chewed up. I’ll try it again.


    private static IEnumerable Range ( DateTime start, DateTime end, int offsetDays = 1 )
    {
    var current = start;
    while (current < end)
    {
    yield return current;
    current.AddDays(offsetDays);
    }
    }

  5. dedeibel Says:

    Hi,

    Solution 2 is the best for me. Simply because it is the easiest and most readable one. In my opinion an easy solution is one that can easily be extended and errors can be spotted fast.

    Solution 3 is good too, lazyness is a good thing for optimization. But in this case I am not sure if it works.
    I am not so deep into ruby right now, but I could imagine that “take_while { | d | d

Leave a Reply

You must be logged in to post a comment.