CSS and the Lambda Calculus
Summary: Using LESS, we can almost achieve the expressive power of the Lambda Calculus as applied to styling. The expressive power is enough to create reusable styles applied to reusable HTML components.
Let's continue our exploration and analysis of CSS and LESS. This series is about Functional CSS. Our aim is to determine a good way to use HTML and CSS so that we can reuse both. We can't talk about "Functional" without talking about the Lambda Calculus.
The Lambda Calculus includes three things: variables, abstractions, and applications. Let's look at some Javascript.
(function (x) {
return x + x;
})(10);
In the above code, x
is a variable, the function definition is a
lambda abstraction, and calling the function (with the parens at the
end) is called application. Luckily, Javascript gives us an additional
kind of abstraction where we can name an expression to be reused:
var f = function (x) {
return x + x;
};
f(10);
We can name the function f
, then apply it by referring to the name.
What's more, we can compose them pretty well.
var g = function (x) {
return x * x;
};
var h = function (x, y) {
return g(x) + g(y);
};
h(10, 20);
We're composing function g
by applying it inside of the definition
of h
. You're probably saying "duh!"---and rightly so. It's so common
to do.
Which is why it's hard to understand why CSS does not include all of these parts. Let's at least try to decompose CSS into some parts.
.some-class {
width: 10px;
height: 20px;
}
Here, we're applying the rule which contains two properties (width
and height
) to all of the elements that have the class .some-class
.
.some-class
is the argument to the rule's application. In fake
Javascript, it would look something like this^1:
function(element) {
element.width = 10;
element.height = 20;
}(document.getElementsByClassName('some-class'));
That is, we are immediately applying the abstraction to the argument.
CSS has no way, as Javascript does, of naming the abstraction for use
later. We definitely want that. What's more, there's no way to
compose two rules together. In Javascript, we could refer to g
within h
. But in CSS, there's no way to do that.
LESS does have this ability. Since LESS is a superset of CSS, we can start with the above CSS code. Let's repeat it:
.some-class {
width: 10px;
height: 20px;
}
Now we abstract it by naming it and then apply the name:
.small-box() {
width: 10px;
height: 20px;
}
.some-class {
.small-box();
}
That's somewhat better. We've got a reusable component (.small-box
)
and we've also added a bit more meaning to our code because we have a
meaningful name. What's more, LESS lets you compose:
.small-box() {
width: 10px;
height: 20px;
}
.my-button() {
.small-box();
border: 1px solid black;
}
.some-class {
.my-button();
}
Here we've made an abstraction called .my-button
by using .small-box
inside. That's composition. And that's on par with our Javascript
examples above.
There's one more thing that we might want that even Javascript doesn't
have. Javascript has first-class functions. That's really nice. But
there are many things in Javascript that are not first-class. For
instance, the arithmetic operators (+
, *
, -
, /
) cannot be passed
to a function or assigned to variables. They are treated differently
by the language. They are a bit like properties in CSS: all you can do
with them is refer to them directly in an expression.
But what if Javascript did have first-class arithmetic functions? We can certainly fake them out.
var plus = function (a, b) {
return a + b;
};
var times = function (a, b) {
return a * b;
};
var minus = function (a, b) {
return a - b;
};
var over = function (a, b) {
return a / b;
};
Now we have functions that act just like the operators---and they're first-class. It's also way more consistent: every operator is a function.
We can do something similar in LESS. Imagine we took every property and did what I do to these:
.width(@w) { width: @w; }
.height(@h) { height: @h; }
. . .
Now we have the consistency that everything is at least referrable as a mixin. This is pretty good.
We can now define our styles---and name them---in this subset of LESS. Styles may reference other styles. And rules with selectors may reference styles. You could import pure styles from an external library, then refer to them in your rules, where you apply them to selectors. The HTML does not have to change, and neither do the styles.
Rules---where styles and selectors are tied together---will change the most. As common sets of styles are used together often, you might think about factoring them out into a new style, which would be added to your organization's standard style library. Also, as HTML components solidify, you could begin to firm up the class names and their structure, reusing them in a more permanent way. Yet, even though these two assets (the standard styles and the standard class names / HTML structures) are permanent, you can still change how they are styled by changing the rules. Both assets retain their value over time.
A fly in the ointment
Even though we have tremendous power over that given by CSS, we are not at the level of first-class styles. The following will NOT work in LESS, though we would expect it to.
/* refer to variable like a mixin */
.apply(@style, @arg) {
@style (@arg);
}
.width(@w) {
width: @w;
}
/* pass in .width mixin */
#xyz {
.apply(.width, 10px);
}
The problem is that you cannot use a mixin named with a variable. The same limitation exists in SCSS (SASS). The following is invalid.
@mixin apply($s, $a) {
@include $s($a);
}
Why can't a
mixin be assigned to a variable? Mixins appear to be in a
different namespace from variables, and only mixin syntax can appear in
the mixin position. Though I don't think apply
itself would be very
useful, it would indicate a recursive abstraction power that could be
used well.
Not being able to write apply
hints that the developers of these two
languages are thinking at the syntax level. They have added some great
features, but they have not truly modeled the problem
semantically. Instead, it
feels like a mix of special-cased syntax rules and string interpolation.
You can assign a ruleset to a variable, but not a
mixin?
Conclusion
LESS (and SASS, etc) have some very powerful features that are miles above CSS in terms of abstraction and composition. I hate using plain CSS after using LESS---LESS is just so much more expressive. You can do some impressive and useful things with them, and I continue to use them. I want to write about how I use a subset of LESS and a strict discipline in HTML to make styling easier and more maintainable. In addition, I'd like to explore what a better-designed style language might look like.
- Please ignore the fact that CSS does operate on sets of elements, not simple elements.