A Litmus test for a programming language

It seems like every few months someone asks me to help decide what programming stack to use for their new startup. And no amount of effort will convince them that it doesn’t matter for their app.

Because, really, it’s not all that important in the long run - after three years it will all be the same.

The one interesting exception where it does matter is in the beginning of a project. Where a few people are trying to get a lot of work done quickly. Libraries can help a lot; familiarity can help a lot; but so can how expressive the language is. Just like the right library, expressiveness can help you make super human progress early on.

Here’s my litmus test for seeing how practically expressive a programming language is. These tests themselves are by and large trivial, but they are representative of a large class of problems that come up early on in a project. Not how fast the language is, or how correct it is, but how easy it is to express some high level abstract concepts in a way that is natural and idiomatic.

Can you write the equivalent of a Lisp with-open block?

Can you write a with-open block that automatically handles closes a file when you’re done with it? Lot’s of languages can do this.

In Clojure:

    (with-open [stream (reader file-name)]
      (println (my-read-line stream)))

In Ruby:

    File.open(file_name) do |stream|
      puts stream.readline
    end

It should be extensible to any sort of resource allocation problem (semaphore, locks, current directory, etc.).

Interestingly, this is a fairly recent addition to Python.

Write logdebug

Can you write a logdebug function that only evaluates it’s args if there is a run time value set to true?

This is a bit trickier, but here’s the code in C:

    int DEBUG_FLAG = 1;

    #define log_debug(fmt, ...)                                     \
            do {                                                    \
                    if (DEBUG_FLAG)                                 \
                            fprintf(stderr, fmt, __VA_ARGS__);      \
            } while(0)

    log_debug("debug: %s\n", expensive_function());

and again in Ruby:

    $debug = true

    def log_debug(&blk)
      if $debug
        $stderr.puts blk.yield
      end
    end

    log_debug {"debug: #{expensive_function}"}

These two examples take different approaches. The C code essential expands the source code to do what you would do by hand - it packages up the logic in a C macro. The Ruby code relies in a language feature that allows us to delay execution in a syntactically convenient way.

Collect all odd integers squared

Processing lists is a common operation. Can you express your logic in a way that makes your intention clear? List comprehension and lazy lists are great for this purpose.

Let’s use Python to collect the square of odd integers:

[x**2 for x in nums if (x % 2) == 1]

Python and functional langauges are great at this.

Complex list procession

Can you return the last N unique lines of a file that have been transformed and determined to be good in some way? For example, you might want to return the last N unique positive integers. This is a nice test because it combines on-line and off-line processing where it’s easy to run out of memory if you do it wrong.

You can use lazy sequences in Clojure:

(with-open [in (clojure.java.io/reader file-name)]
  (->> (line-seq in)
       (filter #(re-find #"^-?\d+$" %))
       (map #(Long/parseLong %))
       (filter pos?)
       (distinct)
       (take-last 10)
       (doall)))

Complex logging

Can you keep state while delaying code evaluation? A more complex logging example is to time some code and write a log entry every 1000 times a logging method is called. Once again, we’ll use Ruby:

class Progress
  attr_accessor :i

  def initialize
    @i = 0
  end

  def log(&blk)
    @i += 1
    if (@i % 1000) == 0
      log_debug(&blk)
    end
  end

  def self.log_progress
    log_debug {"Start"}
    start = Time.now
    my_logger = Progress.new
    yield my_logger
  ensure
    elapsed = ((Time.now - start) * 1000).round
    log_debug {"Done. Total: #{elapsed} ms, #{my_logger.i} iterations"}
  end
end

Progress.log_progress do |logger|
  12345.times do |x|
    logger.log {"got one #{x}"}
  end
end

Conclusion

There are many, many more examples that can come up. Variants of the Expression Problem are an especially rich area. But the simple tests here have always been enough. If you don’t know how to write all these in your stack of choice, then you don’t know your language well enough or it’s not expressive enough. Either way, keep thinking.

Published

06 Jul 2015


Previous Archive Next