The Electro-Future

Where is my flying car?

After a recent code review, my co-workers and I had a rather animated discussion about the merits of relying on falsey-ness in code. Specifically, we were debating whether it was best-practice to rely on the fact that most languages treat certain values as false when evaluating the variable in a boolean context. I was against relying on this, prefering to check for the specific falsey value you expect, and pretty much everybody else was for it.

Since we’ve had this discussion before, and it’s difficult to illustrate all my points in person, I wanted to make my case on my blog.

A leaky abstraction

The prototypical falsey value is the integer 0, and it comes to us that way from our computers’ instruction sets. Computers don’t have boolean types, because they tend to operate on values stored in registers, which are, mostly, of a fixed size (32 or 64 bits, these days). I don’t know of any computers with a Jump on false or Jump on true instruction. Instead, they have things like Jump on zero, or Jump not zero.

Lower level langauges, like C, just take this idea and run with it. They don’t really provide many types that the hardware doesn’t natively understand, so they don’t provide a boolean type at all. The reason the C code:

int x = 0;

if( x ) {
    /* ... */
}

treats the integer x as a boolean is because the if statement is compling into a Jump if zero instruction (jumping past the body of the if).

This makes the language simpler, and the compiler easier to implement, but it forces the programmer to deal with a leaky abstraction. When you’re writing boolean logic, you’re thinking of values as only true or false, but the language is forcing you to consider the fact that the computer doesn’t actually understand those values at all, even though that might not be relevant to the task at hand.

Program in the language of your domain

There are two points where my coworkers suggested it was unreasonable for me to not want to rely on falsey-ness. The first is in arithmetic: specifically checking for divisibility using the modulo operator. Surely I must be OK with this construction, they said:

int x = 5;

if( x % 2 ) {
    printf("X is odd!\n");
}

The problem I have here is that, to paraphrase from the Pragmatic Programmer, you should be “programming in the language of the domain” (that is, in the language of the problem you’re trying to solve, not in the language of programming).

Your math teacher never told you “X is divisible by 2 if the remainder of X / 2 is false,” because that sentence doesn’t make any sense! In mathematics, integers aren’t true or false, so it would be devolving into programmer-speak to start considering certain numbers to be true or false. Explicitly checking for x % 2 != 0 is keeping the code in the language of its domain, which is mathematics.

This same concept also applies to the common practice of treating empty arrays as false. For example, JavaScript developers often write:

var things = [];

if (!things.length) {
    console.log("You didn't give me anything!");
}

Other languages, like Python, treat an empty array itself as being false:

things = []

if not things:
    print "You didn't give me anything!"

In both of these cases, the language of your domain is lists of things (possibly already lower level than you would ideally be writing), and it does not make sense to consider the length, or the array itself, as false. I don’t tell my wife “The grocery shopping went fast, there were false people in line at the checkout!” Similarly, if somebody were to ask you “What Faberge eggs do you have?” you would probably not answer “false.”

For things like strings and lists, I would actually rather have an empty function, which abstracts away the concept of a length of 0. This is especially useful when length isn’t as well defined: is the length of a Unicode string the number of characters, or bytes? Is the length of a node in a tree the number of direct children, or all children?

Code complexity

The next complaint leveled against me was that my code was “more complex” for being explicit about the condition being checked for. In particular, we were talking about if value is not None versus if value, and the argument was made that the explicit case was “more complex” because there was “more code.” I would argue, in fact, that there was not more code, and it is actually less complex to be explicit.

For example, consider the following two functions:

def noop():
    pass

def do_nothing():
    pass

I think it would be difficult to argue that the second version is “more code” just because the function name is longer. I think it is also impossible to argue that the second function is more complex than the first because the function name is longer.

In the case of the falsey comparison, I think it is actually considerably more complex than the explicit version, and here’s why:

if value is not None:
    print "We have something!"

if value is not None and \
        value != 0 and \
        value != [] and \
        value != {} and \
        value.__nonzero__():
    print "We have something!"

The second version is, roughly, what if value actually means in Python, and, written out that way, certainly looks a lot more complicated than the first if statement.

Principle of least surprise

One of the things that bothers me most about treating non-boolean values as falsey is that it violates the principle of least surprise.

When reading a piece of code, say a function, you have to operate on the assumption that the values have a fixed set of properties and behaviors, and that those properties and behaviors constitute a meaningful type of value. In a dynamically typed language, you (and the interpreter) use duck typing to define this type. That is, the type must contain all the properties accessed on the type by the code. In a statically typed language, the language requires the code to declare the type of the value and enumerate the exact properties and behaviors of that type.

In order to make the code more legible, it helps if, at least for the block being read, the type of the value represents some meaningful concept in the program. For example, an integer is a meaningful concept, understood by most people reading your code, and on which the standard arithmetic operators are well defined. If you are writing software for a human resources department, an Employee is meaningful type (whether you define it or not), and there are reasonable assumptions that can be made about the properties and behaviors of that type.

As I discussed earlier, having a boolean state usually doesn’t actually make sense for whatever conceptual type a value is supposed to have. When reading the HR code, I would expect an Employee to have a name, an ID, a mail stop, etc. I would not expect the employee to be false or true. When it comes to things like numbers and empty lists and nullable values, we’ve tricked ourselves into thinking that there are reasonable boolean representations of these types because computers, and programming languages, have provided them for so long. But, if we think about these types in terms of their real-world counterparts, we see that it really doesn’t make sense for many of them to exhibit boolean behavior.

When it makes sense

Of course, I do believe there are a few instances where a type can have a reasonable boolean representation. As an example, consider Python’s regular expression library: when you call re.match() you get a MatchObject back, or None if the string did not match the RE.

Consider, instead, a version which always returned a MatchObject, but which evaluated as False if there was no match. In that case, you could still write code like this:

if re.match(r"\w+", value):
    print "Value contains at least one word"

But, you could also write code like this:

def print_sub_groups(regexp, value):
    match = re.match(regexp, value)
    group = 1

    try:
        while True:
            print match.group(group)
            group += 1
    except IndexError:
        pass

In the specific domain of an object representing the result of matching a string against a regular expression, it actually does make sense for the match to be false, if there is no match. It also has the added benefit of allowing the programmer to not have to special-case whether the match was successful or not if it isn’t relevant to their code.

Avoiding sentinel values

Which leads to the title of this post, which is that treating values as falsey is often a code smell that you might be doing something else wrong.

Let’s consider a (very abridged) version of the code that initially started the discussion with my coworkers. The idea was that a function was returning a list of URLs which would ultimately be sent back to a client browser. In certain situations, in order to work around bugs in older OSes, we wanted to downgrade the connection from HTTPS to HTTP (when the initial request was already over HTTP). The code looked something like this:

def gather_urls(resource_ids, protocol_override=None):
    resources = db.get_resources(resource_ids)
    resource_urls = []

    for resource in resources:
        url = resource.get_url()

        if protocol_override:
            url = replace_protocol(protocol_override, url)

        resource_urls.append(url)

    return resource_urls

The line in contention was the if protocol_override, of course. The code calling this function determined if the protocol needed to be overridden, otherwise this function should return the pre-configured URLs for the resources.

The smell here is that this code has to be told whether or not to override the URLs at all, something it isn’t particularly concerned with (it was actually gathering more than just URLs from the resources). Instead of even needing to check whether the protocol needs to be overridden (the responsibility of other code), the code could be rewritten like this:

def identity_filter(url):
    return url

def force_protocol(protocol, url):
    return replace_protocol(protocol, url)

force_http = partial(force_protocol, 'http')


def gather_urls(resource_ids, url_filter=identity_filter):
    # ...
    url = url_filter(url)
    # ...

This code has a few advantages over the previous implementation: first, the decision of whether or not to override the protocol is completely up to the calling code, with no conditionals. Second, how you actually change the URL has been moved out of the function, so other URL filters can be constructed, or you could even chain filters together.

Hidden behind the falsey check in the first version was actually a sentinel value, None, which pointed to a larger problem with the implementation. Most falsey checks are really checks for a particular falsey value (or maybe a couple falsey values), which all send the code down a different code path.

Style

In the end, most of this comes down to personal preference. Except for a few corner cases, where the programmer doesn’t anticipate a particular falsey value being passed in, both styles of code are correct.

To me, the implicit check for falsey values reads like a number without a unit. The programmer is probably checking for a particular value, but just left it off as short-hand. I find it aggravatingly terse, and I have to stop and think what the programmer meant. If I’m debugging a problem with a particular input, I have to consider what the current value is, whether it would evaluate as false, and whether that is the correct behavior.

This is more difficult if you have to switch back-and-forth between languages often, because not all languages interpret the same values as falsey. For example, in Perl, "0" is falsey, but "0.0" is not (of course!). There is something to be said for taking full advantage of the language you’re using (instead of limiting yourself to the least common denominator of all languages), but relying on certain esoteric, and implicit, rules might make the code more difficult to maintain without any clear advantage.

Expressiveness is often used as an argument for allowing falseyness, and it’s certainly more expressive of a language to allow values to be falsey. However, there is a difference between expressiveness and clarity: just because a language allows you to express the phrase “if this list is false” does not mean that that is the clearest means of expressing what you actually mean (“if this list is empty,” or, better, “if there are no apples”).

Comments

Over the years I’ve met more than a few people who dislike “single-use functions.” The typical arguments are that they are inefficent and make the code more difficult to read, and some of these people will even call them out on a code review. However, I will often write single-use functions, and here’s why.

First, it must be acknowledge that function calls do add overhead, especially in a lot of interpreted languages, so it’s difficult to outright deny the efficiency argument. The only thing I can say to that is, “premature optimization is the root of all evil.” As long as performance is acceptable to your users, I would rather have slightly slower, but more maintainable, code any day of the week. I bet your users would rather you be adding new features and fixing bugs, too, as long as you keep things “fast enough.”

The simplest reason to write single use functions, and also the easiest to rebuke, is that they make code easier to read. If you take a complicated procedure and break it up into higher-order operations, it will be easier for somebody skimming the code to get the gist of what the function does:

def activate_crm114(code):
    enter_code(code)

    test_self_destruct()

    enter_all_receiver_circuits()

If you don’t care about the details, this code is pretty straight-forward. The argument against this is that, if you do care about the details, it’s harder to know exactly what is going on. I certainly agree with that: if I’m reading through the code, trying to trace down a bug, I’m not going to take those functions’ names at face value (there’s a bug somewhere after all, it could be in one of them). Having to jump around the file to see what happens inside them certainly can slow things down.

However, there’s another advantage, and it directly affects readability and debugability: encapsulation. This is an idea I picked up from a blog post by Michael Feathers, although he doesn’t explicitly talk about it in terms of single-use functions. The idea, as he states it, is that the various blocks of conditional statements can be coupled to each other via shared variables. He’s arguing for reducing the conditionals in your code (definitely a good idea), but it also points to an advantage of single-use functions.

Inside the function above, I know that the only function which uses the code variable is enter_code, and the interpreter is going to enforce that for me. If I’m looking for a bug that I know involves the use of that variable, I can safely ignore test_self_destruct and enter_all_receiver_circuits because they can’t possibly access that variable. The use of the variable has been encapsulated into enter_code and activate_crm114.

This also makes it easier to change the interface to code because I know that I won’t affect the logic in either of the functions which don’t take it as a parameter. If all of that logic was inline to activate_crm114 I would have to carefully read through it to find and fix any code I might break while changing interfaces.

Finally, single-use functions can force a code-smell when your logic starts breaking encapsulation. Let’s say that the logic to the functions was all inline to the outer function, and the various bits of logic were growing more complicated, and starting to access a lot of shared variables. At a certain point, it will be obvious that something is wrong: the function will get hard to understand, hard to add to, and hard to fix when it’s wrong. It would be harder, though, to see exactly where all the “spaghetti” was getting tied together.

If the functions are broken out, as they are above, and they start operating on the same variables, then you’ll have to pass those variables around to the individual functions:

def activate_crm114(code):
    interface = PanelInterface()

    activation_token = enter_code(interface, code)

    test_result = test_self_destruct(interface, activation_token)

    enter_all_receiver_circuits(interface, activation_token, test_result)

Having to pass all those variables around can start to feel very cumbersome very fast, so it’s easier to see when you need to start refactoring things. You might not think about referencing a variable further down inside a function, but you’ll probably think about adding a new parameter to the function, passing it at the call site, and updating all the unit tests to pass the new parameter in.

Making the parameters explicit also makes it obvious which concerns are mixed up, and where. If an instance of PanelInterface is needed for all the functions, maybe they should be members of that object? activation_token and test_result being passed around imply that maybe some other object is needed to help maintain state, or maybe enter_all_receiver_circuits is doing some work that would be better done somewhere inside test_self_destruct.

Functions are often taught as providing code reuse by allowing code to be invoked from several different places. However, it’s important to remember that they also provide other features, which can be just as valuable: scoping provides encapsulation, parameters document dependencies, and the entry point provides a place to perform unit testing.

Comments

I spent about the last 3 years at my previous job working almost exclusively on a large C# application. While I was there, if you had asked me what I thought of C#, I would have said I hated it. Now, with a little over a year separating me from that experience, I’ve upgraded my opinion to the milder “I don’t care for it.”

For whatever reason, I’ve recently been thinking about C#, wondering what it was, exactly, that I didn’t like about it, and wondering what that says about me, or the language.

Here are the things that I remember bothering me about C# at the time, and how I feel now. Keep in mind that my experiences were limited to a couple projects in the same organization. They are certainly coloured not just by how we used the language there, but by all the other administrative, political and technical decisions that we made on the projects.

Verbosity

This certainly felt annoying at the time, but, in retrospect, is something that really isn’t that bad. Compared to Java, C# is like a language designed for code golf: first-class functions, lambdas and object literal initializers remove a lot of the boilerplate that Java requires. C# also doesn’t feel any better or worse than C++, which I enjoy writing.

I think part of the issue might have just been that I was mostly working in a large, old codebase, which was not a very productive development environment. Adding the verbosity of a statically typed, strict-ish OO language made development feel like treading through molases.

Language Features

This is definitely something that nobody can really complain about, because C# has all of them. Microsoft loves to practice what I call “checkbox engineering,” presumably so that their marketing departments write websites like this. As a result, C# has pretty much every language feature anybody ever thought of: OO, properties, mixins, first-class functions, lambdas, closures, async co-routines, generics, dynamic typing, static typing. They even added SQL-like syntax to the language itself!

I think the problem here lies, partially, in the fact that many of these features really do feel like they were added to mark off a check-box, and the fact that .NET developers treat them that way. When a new feature comes out there are a lot of blog posts about how awesome it is, and everybody rushes to use them, but the overall structure of the applications is still a monolithic, strongly OO, “enterprise architecture” with inconsistent use of the new features.

Sealed Classes

Only a large, enterprise organization could produce a feature like this: where you can actually mark a class as “sealed” and prevent any other classes from subclassing it. But, only an idiot would declare classes in the standard database library as sealed, without implementing any meaningful interfaces.

This makes it extermely burdensome to write unit tests for your database-interfacing code. For example, DataTable (which represents, among other things, the results of a query) has a Rows property, of type DataRowCollection. Normally you might want to provide a mock set of rows, but DataRowCollection is both sealed (so you can’t inherit from it) and has no public constructors, so you can’t create an instance of it with your own data.

The only options you have are: actually query data from a database, or write your own wrappers, with interfaces, around all the methods you want to call, proxy them to an underlying instance of the core objects, and write all your code against these wrapper classes.

This might be a little nit-picky, but I think it illustrates the attitude the language and library designers had, at least early on in the development of the language. There was an obvious desire to prevent developers from doing things the designers felt was untoward, but that also means they are preventing developers from doing useful things with the language.

Visual Studio

Maybe this isn’t quite fair, since it’s not actually part of the language, but it’s so pervasive in the C# community that it might as well be. There are some great articles about how IDEs can rot your mind, so I don’t want to re-hash all that, but I did have a lot of terrible experiences with Visual Studio, and am very glad to not be using it anymore.

First and foremost, Visual Studio is an enormous, slow, memory-gobbling beast. Since we developed against a trunk with maintenance branches, I would often have to rapidly switch between writing a feature in trunk, fixing a bug in one or more maintenance branches, and sometimes manually merging the fix back to trunk. This meant I either had to constantly be re-opening the solution file for the given branch or leave all the instances running all the time. Since it could take minutes to open a large solution, and each running instance took up hundreds of MB of RAM, this was a damned if you do, damned if you don’t kind of situation.

(Yes, I know VS afficionados will tell you how much faster it is than Eclipse. Talk about faint praise.)

The other problem with Visual Studio is what it does to your code: there’s no need to organize anything if can just “go to definition;” there’s no need to keep classes small if autocomplete can find the 1 method in 100 you were looking for; it’s better to keep all the projects in one monolithic “Solution File” than try to run multiple instances for multiple independent components.

I’d also like to pick one more little nit: build configuration management. When I’m defining my configuration, I want to build up a set of values for all build types, and then override specific ones for individual build types. If I change a default value, I want it to change for all builds. If I want to change a value for just one build type, I want all other build types to get the default. This is very easy to do with an imperative (or even functional) build system:

CFLAGS=-Wall

ifeq ($(BUILD_TYPE), "DEBUG")
    CFLAGS=$(CFLAGS) -O0
endif

This is almost impossible to do with Visual Studio’s build configuration management. You can define default values once, and they will get copied when you make a new configuration, but changing them in the initial configuration won’t change them in the copies. I’m told there’s some complex feature you can enable that lets you accomplish this in their GUI, but if they’d just give you make, or something like it, it wouldn’t be complex at all. It would be just like writing code!

Community

As I said at the beginning, this one is hard for me to judge, because my experience was as much about the team I worked on as the C# community at large. I also think that tools like Mono, NuGet and Microsoft’s semi-open-source initiatives have been improving this situation a lot over the last few years.

That being said, writing in C# almost always meant writing a large, monolithic application against a SQL database, running on Windows. Microsoft’s documentation almost invariable includes an example of binding whatever language feature they are talking about to a DataTable from a SQL query. Compilation is done through a GUI, configuration is done through a GUI, deployment is done through a GUI, everything needs to be a GUI application that you can click, and automation is an afterthought that some other team has to worry about.

When it comes down to it, this just isn’t the kind of software I want to build. I’m certain I could use C# to build software the way I want: using small, independent components, automating the development workflow, maybe even working entirely from the command line. But, the fact of the matter is, I don’t want to have to fight the tooling to build things the right way, which is fundamentally what I found so frustrating about writing C#.

Comments

I’ve decided to try starting up my blog again, and, to mark the occasion, I thought I’d redo the design. If you remember what the old one looked like, you’ll see that the layout is pretty much the same, but I wanted to throw out Twitter Bootstrap and design the look and feel on my own.

Partially this was just to give the site a more unique look, but I also wanted to try really designing something from scratch. I’ve done bits and pieces of CSS at work, but never really had a chance to start from a concept in my head and proceed to a finished product. I also wanted to get more familiar with SASS and CSS3 animations.

My main goals were to design something simple and flat while still looking polished, and to make the design responsive to tablets and phones, which is where I do most of my blog reading these days. I didn’t want to have a lot of superflous animation, but I did want transitions to feel smooth and natural. One of the hardest things for me, as a non-designer, is to pair things down without having the end result look barren, and I wanted to focus on improving that.

One thing I did not do was start with a drawing or mockup, and I regret that. I think it would be useful to spend time just getting the visual design I want out of my head, without worrying about semantic correctness or maintainability. I’d like to try out Sketch for that, but haven’t been able to justify the $70 for something I’d rarely use.

I’ve used SASS before, and I always feel let down by the end result: in my head is a perfect, organized codebase where every color and length is defined in a variable and re-used, but the actual result is a mess, with lots of padding and margins specified as one-off lengths to get everything to line up correctly. Maybe part of the problem is not doing a detailed drawing ahead of time, or maybe it’s the fact that I don’t have the box model memorized, but I think HTML and CSS really fall short when it comes to expressing the design decisions I want to make.

The thing I want to express most, but can’t, is the positioning of elements relative to each other. I want to say things like “element X should be vertically aligned with element Y,” or, alternately, “X and Y should be aligned to a pre-defined vertical rule.” There’s a certain amount of this that you can do implicitly, if the elements are structured correctly in the DOM tree, but you have to have a good understanding of the browser’s layout engine to really pull it off.

Things get especially complicated if you want to support responsive mobile designs in the same page. Because correct positioning depends on the hierarchy and order of elements in the HTML, and you might want to draw elements in totally different places on a tiny phone screen than a desktop, it’s often a struggle to pick exactly how to structure the HTML so you can easily position an element in both places, using just a media query. It certainly makes you understand why some developers just fall back to separate mobile and desktop sites.

I probably need to devote a separate post to the inadequecy of CSS3 animations, but, suffice it to say, I gave up on my original vision for how some of the animations where going to work. In the end, using the simpler transition attributes was pretty painless, but they still have some odd shortcomings that result in having to define the layout differently than I would have otherwise to make the animations work well.

The whole effort was certainly a worthwhile experience: I ended up with a design I liked, and feel more confident that I can build something halfway decent on my own if I need or want to. Let me know what you think, and how it works on different devices and browsers.

Comments

My main “nights and weekends” project lately has been a mobile application I’m calling “Unravled.” It’s a knitting counter, written entirely in client-side Javascript, and packaged using PhoneGap as an Android (and, maybe later, iOS) app.

One of the interesting side-effects of using HTML in a WebView to render your application is that, by default, Android will automatically scale all the HTML elements one higher (or lower) resolution screens. I didn’t think much of this until I started actually testing the app on my Galaxy Nexus, which has a 1.5x scaling factor.

All the text is beautiful and crisp, but the icons are horrible! The trick to making everything look good is to disable the automatic scaling, via a meta tag, and scale it all yourself. For me, the first step was to try to scale the application icon, which I had drawn in Inkscape.

Unravled Icon in Inkscape

The easiest thing to do is just to export the icon in the appropriate sizes for 1x, 1.5x and 2x scaling (there is also a “low-resolution” 0.75x scaling, but I decided not to support it). One of the keys to keeping the lines clean and sharp is to align any vertical or horizontal lines to an even pixel boundary at the exported size, so that you can keep anti-aliasing on for the curves, but not end up with any fuzzy gray areas near the sharp edges. That’s (relatively) simple to do when you are only targeting 1x and 2x scaling (but you have half the pixel boundary positions to work with), but it gets much harder when you also want to target 1.5x scaling. There are very few pixel boundaries that are shared between a 32, 48 and 64 pixel wide icon.

I played around with the idea of “hinting” my icon (by automatically moving the vertexes when exporting it at different sizes), but then I found FontForge, which is a full-featured, open source font editor. The nice thing about fonts is that the built-in font renderer will already handle rendering the font with “hints,” and FontForge can embedded (and even auto-generate) the hints for you. I’ll need to save the details of creating the font for another post, but I was able to convert my custom icon, plus the icons I was using from an existing SVG icon set, to a font in an afternoon. As an added bonus, using fonts for icons lets you change the color and size of the icons easily in your CSS, instead of modifying and re-exporting your SVGs.

Once I had the icons out of the way, the next problem was the CSS. When auto-scaling is off, WebKit will automatically multiply any absolute sizes defined in your CSS by whatever the scaling factor is. Once you turn it off, you have to manage the different absolute sizes yourself. Additionally, with scaling on, any CSS media queries will “lie” and say that the viewport is smaller than it actually is.

The biggest problem here was that I am using Bootstrap, which, while very cool, specifies a lot of things in absolute pixels (especially for the grid system), and all of those sizes needed to be scaled. While playing around with the images, I tried scaling the navbar using less mixins, which worked reasonably well, but required a lot of typing: every element I wanted to scale had to be re-calculated in my master CSS, assuming it was defined as a mixin in the first place, which many elements aren’t.

I knew I wanted to implement the scaling at “build time” so that I could easily add my own custom CSS, or upgrade Bootstrap, without going through an involved upgrade process. My first thought was to take advantage of less and the fact that I was already recompiling all the less code at each build. When you invoke the less compiler via Javascript, you actually get hooks into the various stages of the compilation pipeline, so you can get the AST that the parser generates and operate on it, before compiling it down to CSS. The problem here is that the AST is entirely undocumented, and probably subject to change. Additionally, there are different ways that the AST represents numbers: sometimes as a string value (i.e. 16px) but sometimes as the result of a computation. In that case it’s a sub-tree representing the expression, with the leaves being either variable references or numeric literals, which are JS objects containing an integer value and a string unit.

In the end, that was all very complicated. Once you get down to the compiled CSS, you just need to multiply all the values that are specified in “px” by whatever the scaling factor is. Since Node isn’t very handy for transforming files (the standard library doesn’t even offer a means to read a file line-by-line), I fell back on this simple Python script:

import sys
import re

pixels = re.compile(r'(\d+)(px|\.png)')

scale = float(sys.argv[3])

with open(sys.argv[1], 'r') as input, open(sys.argv[2], 'w') as output:
    for line in input:
        line = pixels.sub(
            lambda m: '{0}{1}'.format(
                int(int(m.group(1)) * scale), 
                m.group(2)), 
            line)
        
        output.write(line)

Sometimes the simplest solution is the best! The lambda is a little messy, and should probably get converted to a named function. It wouldn’t be too bad, except for the need to convert to an int so many times. Checking for “.png” files was a holdover from using SVGs rendered at different scales. It lets you specify file names like: foo32.png in the source (1x scaled) CSS, and have it converted to foo48.png in the 1.5x CSS file.

Once I had the images and the CSS scaled, I was pretty much done. This would definitely be a lot trickier if you were specifying any sizes in your HTML (especially in your HTML templates), but you shouldn’t be doing that, now, should you?

Comments
Check me out