1

I was watching Zoran Horvat's "Making Your C# Code More Object-oriented" on pluralsightpaywalled. And he says that instead of :

if(obj != null) { obj.DoSomething(); }

We should have a list that has either 1 or 0 no. of items of that type:

list.forEach(()=> obj.doSomething());

If the list has no objects it would not perform operation but if it does then it will have perform the operation. This will eliminate the need for branching over null.

But what I need your help with is, understanding the need to avoid branching over null here? Wouldn't a forEach be same as if here?

What the benefit?

  • Which problem are you trying to solve? An empty list or null items within the list? – richzilla Dec 30 '19 at 13:23
  • 2
    Wouldn't a forEach be same as if here? -- No, because list is assumed to always be a list, even if it has zero items. Put a null into list, and you have the same problem as the first example. – Robert Harvey Dec 30 '19 at 15:05
  • The paywall may make it difficult to guess what the original author was claiming. – Nat Jan 2 at 13:47
4

Wouldn't a forEach be same as if here?

As far as branching goes yes. Loops have branches.

If you seriously need to avoid branching (maybe because of some branch prediction optimization issue) the branchless fix is the null object pattern where you create a class that has a DoSomething() method that does nothing, quietly.

Do that and the code becomes:

obj.DoSomething();

Now, when you need to do nothing, obj can be the quiet null object or the classic noisy null that throws a null pointer exception. Either way there was no branching. Just jumping to a set address that the branch predictor saw coming a long way off. This is the polymorphic object oriented solution.

So what does using

list.forEach(()=> obj.doSomething());

give you?

It gives you a way to create a "quiet null" without having to create a special class for it for every type that needs it. This takes the form of an empty collection. You've likely already used a version of this in the form of an empty string: "". Here they're doing the same thing with a list.

I wont say how well branch prediction handles that because engines get improved all the time. But seriously, don't worry about it unless you have to. Worry about readability first and those that come later can make it as fast as it needs to be because they can read it.

What all of this is ignoring is input validation. Unless you can trust your inputs you should check them. When null is a possible input, but not a valid input, you need to check it. This confuses the issue when your type system doesn't let you say that you don't accept noisy nulls.

In an ideal world excluding the noisy null could happen at compile time. Unfortunately when C#'s type system was first created they decided every reference type should be nullable.

But C# 8 has a new feature – non-nullable reference types. Now Foo isn't nullable. Only Foo? is. So in C#8 you don't need input validation for nulls. You can say you don't accept noisy nulls with the type system. The only reason you get noisy nulls is because you decided to accept them.

Fortunately these non-nullable reference types only exclude the noisy null. When you need to quietly do nothing the quiet nulls still work just fine.

  • Thanks that is what I have been wondering all along. When we have obj?.doSomething() why we need to perform gymnastics? – SamuraiJack Dec 30 '19 at 17:57
  • 1
    obj?.doSomething() is just the syntactic sugar form of if(obj != null) { obj.DoSomething(); }. The evil here isn't the branching. It's accepting a noisy null when what you really need is a quiet null. It shouldn't be the clients job to decide what null means. The clients job should only be to decide what kinds of null to accept. – candied_orange Dec 30 '19 at 18:08
  • Is "noisy/quiet null" a thing? – Robert Harvey Dec 30 '19 at 22:57
  • @RobertHarvey It's my thing. Been using the term for years to impress upon people that there is a behavior to think about here. – candied_orange Dec 31 '19 at 0:13
  • I like that terminology! I will try to spread it. Do you have a blog post / article / tweet about it? I think that would be a nice addition to our vocabulary. – Jörg W Mittag Dec 31 '19 at 8:52
2

There are a couple of fundamental ideas behind this.

Object-Oriented Purity

The first fundamental idea is that in Object-Oriented Programming, we program by composing systems of autonomous objects that collaborate by sending messages to each other.

However, an if statement is not a message send, therefore, it is not Object-Oriented.

Dynamic runtime-polymorphic message dispatch (virtual method dispatch in C# jargon) is more general, more powerful, and more expressive than conditionals, so it should be used whenever possible.

The Replace Conditionals with Polymorphism Refactoring shows us how to get rid of conditionals in some special cases, and languages like Smalltalk which don't even have conditionals and loops serve as existence proof that it is always possible to get rid of conditionals and loops and replace them with polymorphic message dispatch.

If you are interested in this train of thought, you can find some resources at the Anti-IF Campaign.

The Introduce Special Case Refactoring (also known as Introduce Null Object) can help getting rid of conditionals handling special cases, and specifically null checks.

null is Evil

While the first idea was deeply rooted in OO, this idea is deeply rooted in statically-typed functional programming, logic, and maths.

There is a deep connection between logic and programming which is exemplified by the Curry-Howard Isomorphism, Girard-Reynolds Isomorphism, Wadler-Blott, and many others. Basically, every type system is equivalent to a system of logic, and every logic is equivalent to a type system. In this isomorphism, [types correspond to theorems and (well-typed) programs correspond to proofs of those theorems].

The problem is that null is a valid program for every type (or at least every non-primitive reference type). Which means that null can prove every theorem! This essentially breaks logic and thus breaks the type system.

Consider a function of type Account -> Money. This can be interpreted as logical implication, i.e. as "from Account, I can deduce Money", or a bit more pragmatically "given an Account, I can produce Money". (You can imagine that this is basically the balance function.) Now, I can "prove" this theorem by implementing the balance function properly, but I can also prove it by simply returning null. The type checker will allow me to do that.

Even worse, the type checker will also allow me to implement, say, a function Cow -> Rain that way, which is clearly non-sensical.

That's like saying that burning all the pieces and storming off the playing venue is a legitimate way of winning a LEGO building contest.

The solution is to create a specific type that signifies the (potential) absence of a value. This type is often called Maybe, Option, or Optional. In languages with Algebraic Datatypes, it is modelled as a Sum Type.

Such a type either contains a value or it doesn't. If you look at it in a certain way, it almost looks like a collection that is either empty or contains a single element.

And this is the great power that such a type gives you: IFF you implement it like a collection, then you get all the power of collections for free! (It is really sad that the authors of Java's Optional type did not understand this.)

How do you produce a new value from a value that may be absent? Well, what happens when you map an empty Collection? Nothing! What happens when you map a collection with a single element? You get a new collection with a single transformed element.

So,

maybeAbsent != null ? someFunction(maybeAbsent) : null

becomes

maybeAbsent.Select(someFunction)

How do you perform a side-effect with a value that may be absent? Well, what happens when you iterate over an empty collection? Nothing! What happens when you iterate over a collection with a single element? The side-effect gets executed once with the element.

So,

if (maybeAbsent != null) Console.WriteLn(maybeAbsent);

becomes

foreach (var option in maybeAbsent) Console.WriteLn(option);

This starts to really shine when you have complex chains of computations that may or may not produce a value. Then you have operations such as flatMap (SelectMany in .NET) which allow you to "thread" an optional value through a long chain of computations, or flatten which allows you to remove nested levels of "optionality".

It turns out that such an optional type is actually much more general than a collection: it is a Monad and in fact even a Functor. Which gives you additional powers especially in languages that have special notations for Monads like C# (LINQ Query Expressions), Scala (for comprehensions), and Haskell (do notation).

Combine the powers!

It turns out that Algebraic Datatypes can be nicely mapped to Inheritance. (Scala has some features such as sealed classes and objects that make it even nicer, but those are not strictly necessary.) This gives you the combined power of getting rid of nulls by modeling them as optional types, and using polymorphism by implementing the operations on the two subtypes (for example NoValue and SomeValue<T>) accordingly. E.g. SomeValue<T>.Select(Func<T> f) => new SomeValue(f(this.Value)) and NoValue.Select<T>(Func<T> _) => this.

You might (rightfully) ask yourself: so, how do I get the value out of the Option at the end? One nice property is that sometimes, you actually don't even need to do that! If all you want is to perform a side-effect, for example, then you never need to get the value out, you can just use foreach.

In functional languages, you would typically use case discrimination via pattern matching. You can do the same in OO languages, e.g. via a switch or if (or even pattern matching in C#), but can we get rid of these conditionals? It turns out we can! We just need polymorphism again, and we add a method to our type which gets the value out but takes an alternative default value as its argument, and we implement the two versions like this: SomeValue<T>.GetOrElse(T _) => this.Value and NoValue.GetOrElse<T>(T defaultValue) => defaultValue.

Both of these have another powerful advantage over dealing with null: you can't forget handling it! Type systems of functional languages typically support exhaustiveness checks for pattern matching that make it a compile error if you don't handle all cases. And in our OO example, we have made sure that the only way to get the value back out is to call the GetOrElse method to which we must pass an argument.

Conversely, it is impossible to accidentally pass a potentially missing value to a function that doesn't expect it because Option<T> is simply a different (and incompatible) type than T.

This gives us the four main advantages of using Option types instead of null references to model the potential absence of a value:

  • No conditionals, no explicit checks
  • Easy chaining
  • Exhaustiveness checks
  • Potentially missing values are clearly separated and explicitly marked as distinct types

Alternatives

There are some alternatives to the approach described above.

One would be to explicitly track nulls as was done in Spec# or separate the type system and all references into two distinct spaces (nullable and non-nullable). This does not solve the problem, though: if you have a nullable reference, you still need to check it. Also, it introduces complexity: assuming a starting point of a typical OO language, circa C# 2.0 / Java 5 -ish, whereas the approach described above only needs language features that exist anyway (inheritance and generics) and would even allow you to get rid of a language feature (namely null), this requires adding language features (nullable and non-nullable references).

Another would be adding so-called "null-safe" and "null-coalescing" operators to the language (as was done in recent versions of C#). While these make dealing with null less annoying, they don't solve the fundamental problem. In fact, I think that by making it easier to work with null, they reduce the pain, and thus the pressure and the incentive to fundamentally solve the problem, and thus in some sense actually make the problem worse.

Specifically in .NET, there is also the bool TrySomething(SomeType arg, out SomeOtherType result) idiom, which can be trivially replaced with Option<SomeOtherType> Something(SomeType arg).

0

You appear to be tackling two seperate problems. The first is checking whether an item in the list is null or not, the second is handling the speific case of an empty list. The second snippet wouldnt work in the case of a null list, it would throw a null reference exception.

Computers are fast, programmers aren't. Its almost always better to make things easier for the progammer (being more explicit with the null check) and let the compiler work out how to make it more efficient.

  • 3
    The author did not claim that checking for null is less efficient. He says that it is bad design. – SamuraiJack Dec 30 '19 at 13:18
  • I believe the idea was not checking an item in the list, but checking a general object. Then, to avoid null-objects, wrap in a list. – D. Ben Knoble Dec 30 '19 at 14:06
0

The point is that you should not use null to signal a special case.

There are different approaches depending on the concrete circumstances.

return values

A method should never return a (literal) null reference (unless null is a valid part of the result set that does not need special treatment at the callers side).

Instead of returning null usually throwing an exception is the more appropriate approach. But be careful: do not use exceptions to implement control flow.

An alternative to returning null is to return a Null Equivalent Object which is of the declared return type of the method and has fix properties and/or methods that to nothing. This Null Equivalent Object usually is a constant.

I don't know that much about C# but in Java we have the Optional that can be returned instead of the value that may be null. This optional will pass the value to a Consumer if it is not null so the caller itself does not need to implement the if(x!=null) branch. Surely C# has a similar functionality.

collections

In consequence of the first sentence in the previous part you should not add null to a collection.

If you have methods returning collections you should make sure that you always return a Collection object, even if it does not contain any element.


you should not use null to signal a special case -- That's not what if(obj != null) { obj.DoSomething(); } does. This case is so "not special" that syntax is provided specifically in the language to cater for it: obj?.DoSomething(); – Robert Harvey

Well, at this point you are handling the the special case signaled by the null reference.


Instead of returning null usually throwing an exception is the more appropriate approach. But be careful: do not use exceptions to implement control flow. -- If you write a Factory method, would you rather it return null or throw an exception if it can't create the requested object? – Robert Harvey

I'd rather throw an exception.

If you prefer to throw the exception, is catching the exception and taking action on it considered "control flow?" – Robert Harvey

It depends how you define control flow. After all the catch block is merely a another branch and throwing the exception is just selecting it. Hopefully you don't want to discuss on that level...

The more interesting way to look at it is: what can the (immediate) caller do when it hits the condition under which the factory would throw that exception? I.e. what could the caller do if the requested ID does not exist or some parameter are invalid?

At the bottom line I (for myself) define control flow as the happy day scenario: all went well. Back to a factory this means the object could be successfully created and the caller can use the objects interface without any restriction.


your assertion that throwing an exception is better could be just as readily handled by simply allowing the Null Reference Exception to occur. – Robert Harvey

This would hide the root cause of the problem. You would need additional investigation to find out why this NPE occurs. A debugging session is quite likely.

If the factory throw an exception there is no question where the problem occurred. The factory can us a meaningful exception type and/ or give a meaningful explanation why this exception has been raised. It is much more likely that you can solve the problem without a debugging session, just by looking at the error message/stack trace.

  • you should not use null to signal a special case -- That's not what if(obj != null) { obj.DoSomething(); } does. This case is so "not special" that syntax is provided specifically in the language to cater for it: obj?.DoSomething(); – Robert Harvey Dec 30 '19 at 15:03
  • Instead of returning null usually throwing an exception is the more appropriate approach. But be careful: do not use exceptions to implement control flow. -- If you write a Factory method, would you rather it return null or throw an exception if it can't create the requested object? If you prefer to throw the exception, is catching the exception and taking action on it considered "control flow?" – Robert Harvey Dec 30 '19 at 15:16
  • The equivalent to Optional in c# is a Maybe monad. The only compelling reason to use monads is to improve functional composition. Otherwise, your assertion that throwing an exception is better could be just as readily handled by simply allowing the Null Reference Exception to occur. – Robert Harvey Dec 30 '19 at 15:38
  • @RobertHarvey: please see my updates – Timothy Truckle Dec 31 '19 at 22:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.