Quote of the Week: "What can C# do that F# cannot?"


The F# community quote of the week was from Tomas Petricek in answer to a question on Twitter, see the pic on the right. 

What Tomas says is not 100% technically accurate: you can get NullReferenceException (NRE) in F# if you use C# libraries. C#-defined-types+the “null” literal, or some backdoors like Unchecked.defaultof<_>.

However what Tomas says does match people’s actual experience with the two languages, and it is certainly true that NREs are absent from routine F#-only coding.

For example, this comparison records that one C# project had 3036 explicit null checks, where a functionally similar F# project had 27, a reduction of 112x in the total number of null checks.  The other statistics in the comparison shown are also compelling, particularly the “defects since go live”. The two statistics are not unrelated.

After a recent F# meetup I grilled one of the guys who wrote this comparison to learn more about it. I can confirm that they are not making these stats up: these projects are both ETL (Extract, Transform, Load) systems which are broadly speaking in the same zone in terms of functionality, or if anything the F# implements more features. The F# project really did have a very, very low bug rate. The size difference is not due only to language differences; there are also differences in design methodology, with the C# characterized by the overuse of OO design patterns that is often seen in C# and Java projects.  Common symptoms include the presence of elaborate, unnecessary and buggy “internal design pattern frameworks” inside the OO code.  If you want to learn more about Functional-First design, the guys involved are members of NOOO (Not Only OO).

Sometimes it bewilders me that a massive reduction in explicit null reference checks in comparable programs doesn’t shake the computing industry a little more. When other industries see such productivity gains, change happens rapidly. But regardless of the social dynamics, I strongly believe that for the sake of our children we need to abandon the hell that is trying to write robust software in a language where nulls pervade the code like a disease. Nulls were famously called the billion dollar mistake – but they probably cost the industry $1B every year by now, and rising. They are also a drain on human energy and talent. Luckily for those who get to use it, F# offers one way through this.

Unfortunately a sort of stasis, or lack-of-will seems to be gripping the industry at the moment. People stick with their existing languages, though occasionally make noises about needing non-null types (thus making C# and Java even more complex, when we should be ridding ourselves of this curse altogether). Even worse, language designers duly trot out new typed language designs (e.g. Dart, TypeScript, and even Scala) which allow nulls everywhere, for all reference types. That said, I know there are reasons these languages have nulls, given their design constraints, and some languages offer constructs which help make nulls abnormal, and that is some progress. But that still does not satisfy me – we didn’t take the “easy” path of pervasive nulls for F#, despite living in the world of .NET. And F# users reap pervasive benefits from the simplicity this brings.

In practice F# (and OCaml, and a few others) shows that highly practical programming and software design is possible in a language where nulls do not pervade regular code. I’d encourage people to think hard about the huge time savings that came with that 112x reduction. And for those interested in language design, look carefully at the F# features which collectively eliminate nulls in routine programming (many come from ML, but not all) . Around the edges we make some compromises. But for the industry the hell of nulls should be diminishing, not increasing. People should be paying attention, and language designers should be held to account.  Is the future full of nulls, or not? Can I trust a value to be what it says it is, or might it not be there at all?

Don

 

Comments (15)

  1. Steve says:

    Not only does Scala have null, it elevates the concept to the position of a special sub-class of all reference types, scala.Null — so you can pose tests for null values as tests for the Null type instead; but then you can do as much in C#, relying on null values having no type and filtering with Enumerable.OfType<>().

    I suspect that the pedigree of the languages has influenced their design, with Scala living in the world of the null-happy standard Java APIs, whereas the ML family didn't bring that baggage into F#.

  2. James Moore says:

    Speaking as a Scala user – all that stuff is for Java interop.  Normal Scala uses Option[T] instead of null, and you'll sees the same lack of null checks that you would see in F#.

  3. Curt says:

    Let's not forget that one can design nulls out of a code base regardless of language, and without fancy new types. en.wikipedia.org/…/Null_Object_pattern

  4. Pop.Catalin says:

    .Net should definitely add non nullable reference types. It might be harder to do now than it could have been in V1 but the effort will definitely pay of in the long run in my opinion.

  5. Hi curt,

    can you explain what you mean be "fancy new types"?

    Cheers,

    Steffen

  6. nicolas says:

    @Curt : this seems a way to work around the possibility of null.

    I think the point being made here is not to work around null, but to have null to exist at the type level, so that the compiler can enforce the validity of the operation done.

    You might like the object null pattern, but how can I, as a user of your lib, know about that.

    Equally, you have no way of knowing if other libs have support or not.

    In a small walled garden, with a lot of wrapping effort, this pattern is useful, although painful.

    But industry wide proof, that does not work, as can be seen by the numerous nullRefs, even in very widespread application that have seen extensive debugging.

  7. Nicolas says:

    I just spent 2 hours because of a system that was trying to be smart and allowed null values to be inputed.

    It of course did not warn me of my mistake, that was… somewhere…..

    1 billion a year ? that's probably underestimating it

  8. Andrius Bentkus says:

    NRE = Null Reference Exception for the other people wondering.

    Please always add the definitions for acronyms, no matter how obvious they are perceived in the actual context.

  9. JeroMiya says:

    I think it is slightly unfair to group scala in with java and C#, seeing as how an exception was made for F# code calling into C# code. Idiomatic Scala code using Option[T] works similarly to F# code using the Option<T> class in F# – namely using pattern matching and for comprehensions to destructure the Option, making a null reference exception impossible (as long as you always use pattern matching/for comprehensions to extract the value out of the Option).

    The last sentence is the problem with both F# and Scala – they don't really go far enough. Option[T] is, in effect, only a rudimentary attempt at including a "code contract" or "design by contract" type system, but only for nullable vs. non-nullable type contracts, and even then it's only if you follow convention.

    If you look at languages like Spec#, C#/VB with Microsoft's Code Contracts enabled (I count that as a different language), or the Eiffel programming language those technologies were based on, you'll see that even more is possible. Option[T] and Option<T> don't go far enough because there's nothing stopping you from using them incorrectly, only convention. A language which allows explicit preconditions, postconditions, and invariants, can enforce those at compile time. If you have a function which takes a string and it's marked as only taking non-null strings, you literally cannot pass it a string that could possibly be null under any code path – you'll get an error at compile time.

    Design by contract systems have their weaknesses, particularly when interacting with code that is not annotated with contracts, but also in cases where it's impossible for the compiler to prove statically that a contract is being honored – in these cases you have to give the compiler hints sometimes with things like assertions and assumptions which allow you to isolate those issues locally. That is typically cited as the reason these kinds of languages haven't caught on (that and the complexity of implementing a static contract verifier).  But by and large, these languages are to F# and Scala what F# and Scala are to C# and Java in terms of the compiler being able to statically verify the absence of a larger class of software bugs.

  10. melnakeeb says:

    A very interesting article.

  11. Mark Allread says:

    NRE isn't an acronym.

  12. Marc Sigrist says:

    Over a year ago, I suggested adding non-nullable reference types to C# (see

    visualstudio.uservoice.com/…/2320188-add-non-nullable-reference-types-in-c-). The suggestion has since become one of the top voted-for C#-related items.

    I have now commented my own suggestion, indicating that a different language design — forbidding null by default — would be even better, and that F# sets a good example. Unfortunately, it is not very realistic that such a fundamental design change will be introduced to a long-established mainstream language such as C#.

  13. Shameless plug: trigged by this discussion I wrote "A tale of nulls" http://www.navision-blog.de/…/a-tale-of-nulls

  14. Ryan says:

    @curt although I love the "null object" as a pattern, having the concept built into the language means that the compiler can do all sorts of awesome static analysis to prove your code's correctness.

    You can kind of do it in C# if you use code contracts; but then you end up with a code base full of explicit null checks in your contracts.  Although the contracts are still useful for the situations where your interface requires say an integer > 0 or a non-null string that isn't string.empty.

  15. Keith S says:

    It has baffled me for so long that not only do NULL's exist in C# (by default) that there are different kinds of nulls.  if(s != null) { // yay!, i might work }   if(ISDBNull(s) == false) { // yay another null check }

Skip to main content