Fixing Random, part 1
How to say this delicately?
I really, really dislike System.Random. Like, with a passion. It is so" awful.
The C# design team tries hard to make the language a "pit of success", where the natural way to write programs is also the correct, elegant and performant way. And then System.Random comes along; I cringe every time I see code on StackOverflow that uses it, because it is almost always wrong, and it is seldom easy to see how to make it right.
Let's start with the obvious problem: the natural way to use it is also the wrong way. The naive developer thinks "I need a new random dice roll, so""
void M(){ Random r = new Random(); int x = r.Next(1, 6); ...
Two lines in and we are already in a world of pain.
First, every time M() is called, we create a new Random, but in most of the implementations of Random available for the last couple decades, by default it seeds itself with the current time, and the granularity of the timer is only a few milliseconds. Computers run on the nanosecond scale, and so the likelihood that we'll get two Randoms with the same seed is very high, and therefore it is very likely that successive calls to M() produce runs of the same number. You never want to make a new Random if you can avoid it; you want to make one once, and then re-use it. But it's a bad idea to stash it away in a static, because it's not thread safe!
Fortunately, I have just learned from an attentive reader that this problem has been fixed in some versions of the CLR; exactly which I am not sure. In those versions, a new Random() now seeds itself randomly, rather than based on the current time. Thanks to the BCL team for fixing this irksome problem; better late than never.
Second, the arguments to Next are the minimum value produced, inclusive, and the maximum value produced, exclusive! This program produces random numbers drawn from 1, 2, 3, 4, 5. The correct way to get numbers from 1 to 6 is of course Next(1, 7).
What we've got here is a classic example of Steve Maguire's "candy machine interface" concept. The candy machines at Microsoft used to have an item number - say, 75, and a price, say, $0.85, you put in your quarters, punch in the item number and, dammit, wait, that's item 085 and it costs $0.75, I got it backwards, and now I've got some weird gum instead of those jalapeno chips. This is a true story! I have actually made that mistake in Microsoft candy machines.
But those are trivial problems. The really fundamental problem here is that we're working at too low a level of abstraction. It is not the 1970s anymore, when rand() was good enough. We have sophisticated problems in statistical modeling and the attendant probabilistic reasoning to solve in modern programming languages. We need to up our game by writing code in the "business domain" of probabilities, not the "mechanism domain" of calls to methods that return random numbers.
Coming up on FAIC: We will start by simply improving the existing implementation of Random, but from that humble beginning we'll develop a new class library that makes programming with probabilities much more readable, powerful and efficient in C#.