
Is your programming language unreasonable? (2015)
As should be obvious, one of the goals of this site is to persuade people to take F# seriously as a general purpose development language.
But as functional idioms have become more mainstream, and C# has added functional capabilities such as lambdas and LINQ, it seems like C# is “catching up” with F# more and more.
So, ironically, I’ve now started to hear people say things like this:
No doubt, the same comments are being made in the JVM ecosystem about Scala and Clojure vs. Java, now that Java has lambdas too.
So for this post, I’m going to stray away from F#, and focus on C# (and by proxy, other mainstream languages), and try to demonstrate that, even with all the functional features in the world, programming in C# will never be the same as programming in F#.
Before I start, I want to make it clear that I am not hating on C#. As it happens I like C# very much; it is one of my favorite mainstream languages, and it has evolved to be very powerful while being consistent and backwards compatible, which is a hard thing to pull off.
But C# is not perfect. Like most mainstream OO languages, it contains some design decisions which no amount of LINQ or lambda goodness can compensate for.
In this post, I’ll show you some of the issues that these design decisions cause, and suggest some ways to improve the language to avoid them.
(I’m now going to don my flameproof suit. I think I might need it!)
UPDATE: Many people have seriously misread this post, it seems. So let me be clear:
If you hang around functional programmers, you will often hear the phrase “reason about”, as in “we want to reason about our programs”.
What does that mean? Why use the word “reason” rather than just “understand”?
The use of “reasoning” goes back to mathematics and logic, but I’m going to use a simple and pragmatic definition:
In other words, you can predict the behavior of some code just by looking at it. You may need to understand the interfaces to other components, but you shouldn’t need to look inside them to see what they do.
Since, as developers, we spend most of our time looking at code, this is a pretty important aspect of programming!
Of course, there is a huge amount of advice out there on how to do just this: naming guidelines, formatting rules, design patterns, etc., etc.
But can your programming language by itself help your code to be more reasonable, more predictable? I think the answer is yes, but I’ll let you judge for yourself.
Below, I’ll present a series of code fragments. After each snippet, I’m going to ask you what you think the code does. I’ve deliberately not shown my own comments so that you can think about it and do your own reasoning. After you have thought about it, scroll down to read my opinion.
Let’s start off by looking at the following code.
The question I would ask you is simple: What is the value of y ?
var x = 2 ; DoSomething ( x ); // What value is y? var y = x - 1 ; (scroll down for answer)
The answer is -1 . Did you get that answer? No? If you can’t figure it out, scroll down again.
Trick question! This code is actually JavaScript!
function DoSomething ( foo ) { x = false } var x = 2 ; DoSomething ( x ); var y = x - 1 ; Yes, it’s horrible! DoSomething accesses x directly rather than through the parameter, and then turns it into a boolean of all things! Then, subtracting 1 from x casts it from false to 0 , so that y is -1 .
Don’t you totally hate this? Sorry to mislead you about the language, but I just wanted to demonstrate how annoying it is when the language behaves in unpredictable ways.
JavaScript is a very useful and important language. But no one would claim that reasonableness was one of its strengths . In fact, most dynamically-typed languages have quirks that make them hard to reason about in this way.
Thanks to static typing and sensible scoping rules, this kind of thing could never happen in C# (unless you tried really hard!) In C#, if you don’t match up the types properly, you get a compile-time error rather than a run-time error.
In other words, C# is much more predictable than JavaScript. Score one for static typing!
So now we have our first requirement for making a language predictable:
C# is looking good compared to JavaScript. But we’re not done yet…
UPDATE: This is an admittedly silly example. In retrospect, I could have picked a better one. Yes, I know that no one sensible would ever do this. The point still stands: the JavaScript language does not prevent you from doing stupid things with implicit typecasts.
In this next example, we’re going to create two instances of the same Customer class, with exactly the same data in them.
// create two customers var cust1 = new Customer ( 99 , "J Smith" ); var cust2 = new Customer ( 99 , "J Smith" ); // true or false? cust1 . Equals ( cust2 ); (scroll down for answer)
// true or false? cust1 . Equals ( cust2 ); Who knows? It depends on how the Customer class has been implemented. This code is not predictable.
You’ll have to look at whether the class implements IEquatable at least, and you’ll probably have to look at the internals of the class as well to see exactly what is going on.
Why not make the objects equal by default, and make reference equality testing the special case?
In this next example, I’ve got two objects containing exactly the same data, but which are instances of different classes.
// create a customer and an order var cust = new Customer ( 99 , "J Smith" ); var order = new Order ( 99 , "J Smith" ); // true or false? cust . Equals ( order ); (scroll down for answer)
// true or false? cust . Equals ( order ); Who cares! This is almost certainly a bug! Why are you even comparing two different classes like this in the first place?
Compare their names or ids, certainly, but not the objects themselves. This should be a compiler error.
If it isn’t, why not? You probably just used the wrong variable name by mistake but now you have a subtle bug in your code. Why does your language let you do this?
UPDATE: Many people have pointed out that you need this when comparing classes related by inheritance. This is true, of course. But what is the cost of this feature? You get the ability to compare subclasses, but you lose the ability to detect accidental errors.
Which is more important in practice? That’s for you to decide, I just wanted to make it clear that there are costs associated with the status quo, not just benefits.
In this snippet, we’re just going to create a Customer instance. That’s all. Can’t get much more basic than that.
// create a customer var cust = new Customer (); // what is the expected output? Console . WriteLine ( cust . Address . Country ); Now the question is: what is the expected output of WriteLine ?
// what is the expected output? Console . WriteLine ( cust . Address . Country ); Who knows?
It depends on whether the Address property is null or not. And that is something you can’t tell without looking at the internals of the Customer class again.
Yes, we know that it is a best practice that constructors should initialize all fields at construction time, but why doesn’t the language enforce it?
If the address is required, then make it be required in the constructor. And if the address is not always required, then make it clear that the Address property is optional and might be missing.
So let’s add another item to our list of improvements.
// create a customer var cust = new Customer ( 99 , "J Smith" ); // add it to a set var processedCustomers = new HashSet < Customer >(); processedCustomers . Add ( cust ); // process it ProcessCustomer ( cust ); // Does the set contain the customer? true or false? processedCustomers . Contains ( cust ); So, does the set still contain the customer at the end of this code?
// Does the set contain the customer? processedCustomers . Contains ( cust ); Maybe. Maybe not.
If both are true, then the hash will have been changed, and the customer will not longer appear to exist in the set (even though it is still in there somewhere!).
This might well cause subtle performance and memory problems (e.g. if the set is a cache).
One way would be to say that any field or property used in GetHashCode must be immutable, while allowing other properties to be mutable. But that is really impractical.
Better to just make the entire Customer class immutable instead!
Now if the Customer class was immutable, and ProcessCustomer wanted to make changes, it would have to return a new version of the customer, and the code would look like this:
// create a customer var cust = new ImmutableCustomer ( 99 , "J Smith" ); // add it to a set var processedCustomers = new HashSet < ImmutableCustomer >(); processedCustomers . Add ( cust ); // process it and return the changes var changedCustomer = ProcessCustomer ( cust ); // true or false? processedCustomers . Contains ( cust ); Notice that the ProcessCustomer line has changed to:
var changedCustomer = ProcessCustomer ( cust ); It’s clear that ProcessCustomer has changed something just by looking at this code. If ProcessCustomer hadn’t changed anything, it wouldn’t have needed to return an object at all.
Going back to the question, it’s clear that in this implementation the original version of the customer is guaranteed to still be in the set, no matter what ProcessCustomer does.
Hacker News
news.ycombinator.com