hisham hm

Generalized nullable operators

Today I was writing some Lua code and had to use something like this for the millionth time:

logger:fine("my message " .. (extra_data or ""))

Since operators in Lua fail when applied to null (and thankfully don’t do wat-eseque coercions), whenever I want to perform an operation on a value that may be null, I have to add the neutral element of the operation as a fall back:

print(a + (b or 0))
print(x * (y or 1))

This got me thinking of null-conditional operators such as ?. that some other languages such as C# have.

Then I wondered: wouldn’t it be nice if “?” could be a modifier to any operator?

Creating null-checking operators

Here’s the initial sketch of the idea: in case of a “nullable operator”, just cancel the operation when given a null operand: i.e., return the left-hand value in case the right-hand value is null.

Or, expressed in Haskell, here’s a function “rightCheckNullable” that takes a normal operator and converts it to a nullable version checking the right-hand value (”nullable types” are represented as “Maybe” types in Haskell):

rightCheckNullable :: (a -> b -> a) -> (a -> Maybe b -> a)
rightCheckNullable fn = a b ->
   case b of
   Just x  -> fn a x
   Nothing -> a

Let’s create some nullable operators:

(+?) = rightCheckNullable (+) -- nullable addition
(*?) = rightCheckNullable (*) -- nullable multiplication
(++?) = rightCheckNullable (++) -- nullable concatenation

And give them a spin:

main =
   let
      v1 :: Float
      v1 = 123

      v2 = Nothing

      v3 :: Maybe Float
      v3 = Just 456
   in
      do
         print $ show (v1 +? v2) -- prints 123.0
         print $ show (v1 +? v3) -- prints 579.0
         print $ show (v1 *? v2) -- prints 123.0
         print $ "hello" ++? Just "world" -- prints helloworld
         print $ "hello" ++? v2 -- prints hello

With something like the above, instead of a + (b or 0) and x * (y or 1), one could write simply:

print(a +? b)
print(x *? y)

This could give back some of the terseness we have when null auto-coerces to other types, without surprises with various operations. In JavaScript, null coerces to 0 when it is an integer, which gives us a proper neutral element for addition but not for multiplication.

Null-checking in C#

Note, however, that my choice of picking the right-hand value and checking the left-hand value only was arbitrary (though it works well for the examples above).

In C#, operations on nullable types are always lifted: the operators of the original types are extended with a check where, if either of the arguments is null, the result of the operation is null.

In Haskell, this transformation would be the following, taking a function that goes from a’s to b’s producing c’s, and producing an equivalent function that goes from Maybe a’s to Maybe b’s producing Maybe c’s:

bothCheckNullable :: (a -> b -> c) -> (Maybe a -> Maybe b -> Maybe c)
bothCheckNullable fn =
    ma mb ->
      case ma of
         Nothing -> Nothing
         Just a ->
            case mb of
               Nothing -> Nothing
               Just b -> fn a b

(In Haskell, you don’t have to actually write this function, since can use Control.Applicative.liftA2, a generalization of the above, to get the same result)

Checking the left-hand value

This makes me think that my “nullable operator modifier” could be aplied to either side (or both). Note that the syntax for null-conditional in C# is already ?., with the question-mark on the left-hand side, since the value being checked for nullity is the left-hand one. We don’t want x?.y to return y when x is null, though. A more sensible semantics for left-hand ? would be a “short-circuiting” one:

leftCheckNullable :: (a -> b -> c) -> (Maybe a -> b -> Maybe c)
leftCheckNullable fn = a b ->
   case a of
   Just x  -> fn x b
   Nothing -> Nothing

The flood gates are open!

There is still an asymmetry here, as rightCheckNullable is the only one that returns the “other value” when one of them is null.

In fact, we could have six versions of the conversion function: right-check, both-check, left-check, each of them returning the “other value” (as I did with +?) or null. If we called the C#-like version +??, this means addition could be modified into: +?, ?+, ?+?, +??, ??+, ??+??.

(And there could be two more variants of course, ?+?? and ??+?, but coming up with a realistic example using them would be a nice exercise in creative coding.)

But would it make sense to have so many modifiers?

Well, for one, instead of writing this

logger:severe("Error: connection failed" .. (details and (" - details: "..details) or ""))

we could write something like:

logger:severe("Error: connection failed" ..? (" - details: " ..?? details))

I know this is a step into the world of APL, and I’m not arguing this is a great or even good idea, but it was fun to think about, so I thought I’d share.