Article

More on Racket

Back in the day, when learning a new programming language, I would take a little time and make a random password generator: this is how I learned my first bit of C as a pimply junior high school student. This predated Project Euler by quite some time (I think the technical term is "a coon's age"), but these days I turn toward those problems to more systematically get a few chunks of language learning under my belt. Time hasn't been very plentiful these days—and in fact I think I'm brushing off some required duties at the moment—but I wanted to formalize a few thoughts.

First, one of the major attractions of Python to me is the rather simple concision of the language. When I was programming in Java for school and for support at work, I was at first really impressed by the way Eclipse could generate all that boilerplate code. Hauling up the IDE, though, was painful. (I only ever found the alternative, Netbeans, useful, I confess, for its ability to parse C macros and dump the code out—this was of inestimable value when implementing the zero on free feature of the OSys custom memory manager.)

Only later did I consider that boilerplate code, while it has its place, is a possible indication that the language itself is a little bloated. Take, for example, automatically generated getters and setters. What programmers really want for the most part is to ignore method calls and instead treat attributes as attributes. The use of properties (implemented in C#, as a user hack in C++11, not at all in Java, and as a decorator in Python) is a convenience for programmers, although the logic goes that properties can break encapsulation and may not be explicit enough to warn users of the interface of potential consequences.

So Python's ability to treat attributes of classes as such without any extra work on my part (or that of my IDE) and to admit changes if needed by way of decorators was welcome. It was short, comprehensible, and required less code. (Of course, Python doesn't really encapsulate anything—it, like Perl, demands quite a bit from programmers to be written well.)

So back to Project Euler; the second problem consists of summing even Fibonacci numbers below four million. The bulk of the work is done in calculating the numbers. This is a great problem for learning about the perils of recursion, tail-recursive optimization, and memoization. The naive solution is computationally very inefficient because it doesn't reuse calculations from previous iterations (we're building, after all, from the end to the beginning).

As is often the case in natural languages, sometimes we want to translate from one language to another; so I thought I would write a Pythonic solution (in a couple of ways—the searches always lead you down rabbit trails, like the Jargon File definition of tail recursion). So here are a couple of implementations with some thoughts:

l = [1, 1]

def fib(n):
    idx = n

    if idx >= len(l):
        add_nth_to_list( idx, l, l[-1] + l[-2] )

    return l[idx]

def add_nth_to_list( n, l, num ):
    if n == len(l) - 1:
        return l[-1] + l[-2]
    else:
        l.append( num )
        add_nth_to_list( n, l, l[-1] + l[-2] )

This is a tail-recursive version, and not one that's particularly great. Probably it's smarter to make an iterative version instead:

l = [1, 1]

def fib(n):
   ln = len(l)
   if n >= len(l):
      for i in range(ln, n+1):
         l.append( l[-1] + l[-2] )

   return l[n-1]

Racket has iterative options (for/list among others) that we could use, but it encourages recursion. So here's a first stab at it:

(define (fib n)
  (letrec ((fib-list (list 1 1))
           (sum-last (λ (l) (+ (list-ref l (- (length l) 2)) (last l))))
           (add-nth (λ (l n)
                      (cond ((= n 0) l)
                            (else 
                             (add-nth (append l (list (sum-last l))) (sub1 n)))))))
    (printf "~a" (list-ref (add-nth fib-list n) n))))

Really it's a two-line difference: the Racket code is eight lines long, and the Python seven (not including whitespace). But which is more readable? I've used some lambdas to make life a little easier to read in the logic instead of embedding everything, but this is a little like reading German: to understand the dependent clause you have to skip to the end to get the verbs and then go back.

There are more elegant ways to do this, I bet. It'd be nice, for example, to use a for/list for iteration instead, or simply map; these would likely shrink the code-size. On the same token, though, Python also has map and filter functions (now objects, I guess), or one can use list comprehensions if desired.

Now probably the real distinction is that Racket will afford more concise code using pattern matching (here's a more explicit example that uses pattern matching for some code generation tools) and macros. (They're not like the evil macros in C/C++.)

I'll be poking and prodding at this as time permits; the conclusion is rushed, but Real Life intrudes on writing here sometimes.