"Lifting" is an esoteric term that is used by a few languages (C♯, most notably) to describe a feature usually associated with nullable types. It's used colloquially to signify that a member (or operator) is "lifted" from another object automatically by the compiler.

What this actually means is best illustrated with an example. C♯'s reference types are nullable by default. This means you can do things like object foo = null; and not get a compiler error. It also means you can do things like foo.ToString() and get an ever familiar NullReferenceException.

This is because reference types in C♯ are nullable by default. But not all types are this way. Value types like int are never null, and have a default value that is retrievable with the default operator: var zero = default(int);

The expected behavior becomes less clear when you use nullable value types like int?. By using nullable types, you can assign a null value to a value type without a compiler error: int? nullableInt = null; For example, what happens in the following code? Is it a compiler error, a runtime error, or is it valid code that will run without exception?

int? nullableInt = null;
int nonNullableInt = 2;
int result = nullableInt + nonNullableInt; //what happens?
int? result = nullableInt + nonNullableInt; //what about here?

The answer I'll leave as an exercise to the reader, as it's mostly irrelevant to what I want to talk about. That is, the subject of this post is not the peculiarities of the C♯ language specification and the validity (or lack thereof) of nullable reference types. Both subjects have been hotly debated by people far more qualified than I. Rather, the subject of this post is what "lifting" means in the context of a programming language. I gave the example above to illustrate potential ambiguities when performing operations on nullable types. The actual value of result is insignificant, but the ways in which the compiler determines how to perform addition between a potentially null value and a non-null value are a good lead-in to lifting.

Let's get mathematical

Lifting comes from the prestigious and panty-dropping field of topology, which has to do with "structured space", which is basically a catch-all term in mathematics. You can hunt down the exact definition later.

In algebraic topology, there's a thing called a homotopy. Two functions in two different topological spaces are homotopic if one can be transformed into the other. What that actually means is not really relevant, but homotopic functions (and in particular homotopies in more than two dimensions) played a role in proving the Poincaré Conjecture. So they've served a purpose at some point.

So where does lifting come in? Well, if you have a homotopy on a space X to another space B and a mapping function δ from another topological space E to B, then δ has the homotopic lifting property on X if it satisifies some other conditions.

Obviously, that makes no sense, and nor should it, unless you happen to be a grad student studying algebraic topology. The exact meaning isn't important, but a general inkling of what it represents will aid in understanding why programming languages borrowed the term lifting.

So. The δ function above is a map bridging two different spaces. Instead of spaces, we'll call them sets (since that's actually what they are). Say E is the set of integers { 1, 2, 3, 4 }, and B is the set of integers { 2, 4, 6, 8 }. In this trivial case, δ could be \(f(x) = 2x\). It should be pretty obvious to see that δ will map each element in E to an element in B.

Now, to say that δ has the lifting property is where it starts to get interesting. Well, more interesting. Whatever.

Anyway, to say a function has the lifting property requires a bit of verification. Specifically, it requires several conditions to be true, all of which I'm not going to discuss. You can read about them on wikipedia if you want. The important thing is that if δ has the lifting property on a space X (which, remember, contains a homotopy from X to B), then there exists another function g that maps X to E.

So, continuing with our trivial example, say X is { 5, 6, 7, 8 }. Then we could have \(g(x) = x - 4\). This would map all of the elements in X to an element in E (which was { 1, 2, 3, 4 }, as was defined above).

Now, bear in mind that this is all completely contrived and dumbed down for the sake of illustrating the concept of lifting in programming languages. This is not a valid mathematical notion. This ain't Wolfram|Alpha. For example, X must be defined on the continuous closed interval \([0, 1]\), and there must also exist a map from \(X \times {0}\) to E. But we won't get into that.

So what is happening is that the homotopy from X to B is "lifted" to E by g. What this (approximately) means in layman's terms is that the homotopy from X to B can also take place from X to E. And what THAT means is a function that can transformed from X to B (the definition of homotopy) can also be transfromed from X to E.

Duh. *pushes up glasses*

Lifting in C♯

So how does all of this relate to C♯ and nullable types? Well, to be able to conveniently perform addition, C♯ "lifts" the + operator on int to int?. If you think of the set of operators on integers a topological space, and the set of operators on nullable integers another topological space, the magic that happens that allows addition between nullable integers would be the δ mapping function that lifts addition to the nullable integers.

I think that might be the worst of analogy of all time. I don't think taking something simple and rewording it be more complex is an actual literary technique. It's possible that I just invented it. But I digress.

Using the term "lifting" is a bit of a misnomer, as it has concrete meaning in other fields (namely algebraic topology). "Lifting" in C♯ is a bastardized form of "lifting" that ignores many of the essential properties of homotopy theory (like the fact that it only applies to continuous functions). But honestly, it doesn't really matter. The visualization of literally (well, figuratively) "lifting" an operator from one object and applying it to another object is pretty apt. But sometimes it's good to understand WHY these things are done this way.