What exactly does 'lifted' mean?

What exactly does 'lifted' mean?

Rate This
  • Comments 11

(Note: all type placeholders S, T, U that I mention in this article are non-nullable value types.)

I got a question the other day from a reader who had been taking a close look at the C# 2.0 standard. He noticed that when we talk about nullables in the standard we talk about "lifting", but we do so inconsistently. To summarize what the standard says:

1) For every equality and relational operator that compares an S to a T and returns a bool there is a corresponding lifted operator that compares an S? to a T? and returns a bool. It returns false if either argument is null. (With the additional exception that if one argument to an equality operator is the null literal then the result may be determined by checking if the nullable term has a value.)

2) For the unary operators + ++ - -- ! ~ where the operator takes an S and returns a T there is a corresponding lifted operator that takes an S? and returns a T?. It returns null if its argument is null.

3) For the binary operators + - * / % & | ^ << >>, where the operator takes an S and a T and returns a U there is a corresponding lifted operator that takes an S? and a T? and returns a U?. It returns null if either argument is null.

4) For user-defined conversions from S to T, there is a corresponding lifted user-defined conversion from S? to T?. It returns null if the argument is null.

5) For built-in conversions from S to T, there are corresponding nullable conversions:
5.1) from S? to T?, which returns null if the argument is null.
5.2) from S to T?, which never returns null, and
5.3) an explicit-only conversion from S? to T which throws if the argument is null.

Why does the standard call out that the conversions in (5) are "nullable" but not "lifted"? Everything else is "lifted"!

This is a bit of a mess I'm afraid.

I talked to Dr. T (AKA Mads Torgersen, the C# language PM) about this. He defined for me precisely what mathematicians mean by “lifted”.

Suppose we've got a function f which maps values from a set A into a set B. That is f:A→B.

Suppose further that null is not a member of either A or B.

Now consider the sets A' = A ∪ { null } and B' = B ∪ { null }

We define the "lifted function" f' as

f':A'→B' such that f'(x) = f(x) for all x ∈ A and f'(null) = null

Similarly, if we had a two-argument function f: A × B → C, we would define f': A' × B' → C' as f'(x,y) = f(x,y) for all (x,y) ∈ A × B and null if either x or y is null.

What we’re getting at here is that “lifted” means “takes nulls, always agrees with the unlifted version when arguments are not null, maps everything else onto null”.

Now you probably see why I said this was a bit of a mess. By the mathematician's definition, (1) is incorrectly called "lifted", (2), (3), and (4) are correctly called "lifted", (5.1) is incorrectly NOT called "lifted", and (5.2) and (5.3) are correctly not called "lifted". Of course we can choose to define what "lifted" means in C# any way we like, but it would be nice if our definition agreed with convention and was used consistently!

I regret the confusion. I do not believe there is any particular sensible reason for these inconsistencies. Rather, the details were changed so many times over the years as the nullable feature was developed that these sorts of subtle problems crept into the spec and were never expunged. Though of course all of us have as a goal that the standard be a model of clarity and permanence, it is fundamentally a working, evolving, imperfect document; these kinds of things will happen. Hopefully in the next version of the standard some of these sorts of details will be tidied up.

I hope that answers the question!

  • So, it's a very similar concept to lifting a function into a monad in Haskell, really? Which makes sense, as a nullable type is very similar to Haskell's Maybe, which is....a monad.

    A simple example - here, (*) is effectively Int multiplication

    aaa :: Maybe Int -> Maybe Int -> Maybe Int

    aaa a b = liftM2 (*) a b

    aaa (Just 10) (Just 23) = Just 230

    aaa (Just 10) Nothing = Nothing

    etc...

    Sweet!

  • To me the nomenclature is only a nicety, as the definition is right alongside the word.  The real question is "What's up with #1, why is in not lifted?".   I hope the answer is interesting, and not the usual "backwards compatibility".

  • I still wish that you would have hidden the pointless HasValue and Value properties so that methods and properties could have been lifted to the nullable object.

    I have a whole long explanation that I always give people how in C# it's *always* illegal to have a null on the left of a "." operator, that that's completely consistent and there aren't any "magic" methods which are different (I had someone say to me recently that he'd understood that ToString() was ok to call on null, which seems to be a common misconception). And now I have to add "unless the object is a Nullable in which case HasValue is ok to call". Yuck.

  • Nulls can complicate code. One of the lesser known C# language features, the ?? operator , can sometimes

  • Thanks, an excellent Explanation !

  • @Eric:  I really enjoyed this one.  :)

    @Kyle:  #1 is not lifted because if it were it would return null if either arguments is null.  However since it returns a bool (a value type) it can only return either true or false, it returns false which is consistent with the proper use of null but not consistent with the term "lifted".

  • The article says that the lifted operator “returns false if either argument is null.” However, this is not true for “==”, which returns true of both arguments are null.

  • @Timwi:

    (With the additional exception that if one argument to an equality operator is the null literal then the result may be determined by checking if the nullable term has a value.)

  • Is there an assignment operator that makes is syntactically simpler?  Opposite of var a = b ?? c where if b == null, a=c, otherwise b...  I'd like this: var a = !?? b : c    where if b is null return it, otherwise c.  The main reason is due to many attributes and properties... such as var action = Config.GetAction("myAction");   return action == null : null ? action.condition    would be easier to write return action !?? action.condition  (i.e. if action is not null, dive into it, otherwise return null).

  • After staring at the above comment from Jim for a few minutes in puzzlement, I think I've finally figured out what he meant to say. First thing that got me was him talking about an "assignment operator". What he's asking for is not an assignment operator (which is just '=') at all.

    The second part is that "var a = !?? b : c" should be "var a = b !?? c". The logic here is that "b ?? c" means "if b is null, return c", but what he's asking for is "if b is NOT null, return c", thus the ! in the !??.

    The third part of the puzzle was "return action == null : null ? action.condition". He got the tertiary operator (?:) backwards. So it should have been "return action == null ? null : action.condition".

    Now it all makes sense. I have only just today learned about ?? (which is how I got here) and I'm looking forward to using it, but I doubt that the !?? operator Jim requested would be of much use to me.

    Hopefully this will save someone else from the head scratching I went through! lol ;-)

  • Thanks Robby, I was wondering what Jim was meaning myself!

Page 1 of 1 (11 items)