Gyepi Sam
2013-05-15
On language contortions

My language can do that!

In the inumerable wars and discussions surrounding the relative merits and abilities of languages X and Z, one frequently runs into code designed to prove the point that some feature of language X can be implemented in language Z.

More often than not, the authors succeed at making their point that, indeed, language Z can achieve similar goals. However, the result often does not look like anything that a Z programmer would want or be able to use on a regular basis. Take the following snippet from the ‘Python Cookbook’ where the authors elucidate function composition.

def compose(f, g, *args_for_f, **kwargs_for_f):
    ''' compose functions.  compose(f, g, x)(y) = f(g(y), x)) '''
    def fg(*args_for_g, **kwargs_for_g):
        return f(g(*args_for_g, **kwargs_for_g), *args_for_f, **kwargs_for_f)
    return fg

def mcompose(f, g, *args_for_f, **kwargs_for_f):
    ''' compose functions.  mcompose(f, g, x)(y) = f(*g(y), x)) '''
    def fg(*args_for_g, **kwargs_for_g):
        mid = g(*args_for_g, **kwargs_for_g)
        if not isinstance(mid, tuple):
            mid = (mid,)
        return f(*(mid+args_for_f), **kwargs_for_f)
    return fg

In both examples the functions f() and g() are being composed, along with a fixed argument, x, to f(). However, there’s a slight difference in how the results of g() are passed to f(); in the first case, g() returns a single value and that becomes the first argument to f(). In the second case, g() returns a single value, which is expanded inline, using the splat operator, into a list. In the implementation of mcompose, we see that that return value of g() is placed in a tuple if it is not a tuple. Incidentally, this would have been better done by expecting a sequence and not specifically a tuple.

I know and like Python and this not a piece of code I would want to write in the language. To my mind, this chunk of code looks tasteless, like a mechanical translation of something that looked more elegant in its native habitat. Of course, it is legal python, but it is not pythonic. This is a case where the language allows you to do something if you want to jump through enough hoops, but clearly, it is not designed for it. In fact, the two main issues with this solution stem directly from the language design. In python, as in C, functions are first class objects, but cannot be dynamically created (except, for the lambda oddity, which has its own limitations). As a result, you end up needing a second function within which to define the target function. Along with that, the language makes distinctions in argument types and groupings, which is quite useful but not when one has to pass along all arguments to an inner function. At that point, python starts looking like it drank a little too much C while trying to channel Lisp!

Faced with this situation, I generally prefer to solve the problem according to the language’s design, knowing, of course, that a different language may have other facilities that would, necessarily, change the solution. Part of the appeal, for me, of learning different languages is to learning and using their idioms effectively. If you think about it, the same holds true for human languages. It is quite clear when someone is competent in a language but not idiomatically so. In a sense, the degree of nuance corresponds with the degree of fluency. While one could argue, in the case of a computer program, that nuance is not a relevant measure because the compiler does not care. This line of reasoning is technically true, but misses the important point that programs are written for and by humans and they frequently care about nuance and style and meaning.