Categories
Show All
Recent Posts
Archives
|
Taking advantage4/25/2005 Thanks to everyone who's emailed or commented supportively. Jeff in
particular, thank you for a much needed laugh, and I too hope that what
I actually have is Nullable<Cancer>. Also Jeroen and Mark for the
thoughtful emails, Jim for the comment on his own blog, and everyone at
work and everyone I know in person for their thoughts and prayers (I
may not believe in prayer personally, but I appreciate the thought from
people who do).
I arrived at work this morning to find that lots of people were sick with
colds, headaches, etc - and that's not including the people who were out sick. The conversation went something like...
Coworker 1: "We're all a bunch of invalids today..."
Me: "Well, I have cancer -- I win!"
Coworker 2: "My husband's sick and he's also having a colonoscopy"
Me: "I have cancer -- I still win!"
Coworker 2: "Fair enough"
Normally when I or members of the family are sick I'll struggle through
and work from home, or sometimes feel guilty and leave Janene to suffer
while I go into the office because there's stuff that simply needs me
to do it. But right now even when I'm in the office I can't really
focus, and besides, if there's anything in life that entitles you to
take advantage and take a little bit of a break to recuperate, it's
having cancer.
So for the rest of this week I've pledged that I'm not going to feel
obligated to get any work done. That's not to say I won't do anything
that will benefit my work, but I'll focus on stuff I want to do with
long-term benefits, rather than the never-ending stream of kludgy
customer-specific fixes that drive my stress levels through the roof at
the best of times.
(By the way, this means among other things that I won't be receiving
any @netreach.com email - if you want to reach me, use the gmail
address at the bottom of every page of my site)
So here's a list of projects, work-related and not, that I intend to attempt over the next few days:
- Get japitools handling some JDK5.0 features. I've started this
already - I have a version of japicompat that can theoretically cope
with a lot of the "interim" japi file spec version 0.9.7 that supports
some, but not all, of the 5.0 features. Unfortunately I don't have any
way of creating japi files in that format: Jeroen, if you're
reading this, do you have any tips on how to get the necessary metadata
out of the class files?
- Get nrdo integrated into the new Visual Studio 2005 beta in the
cleanest possible way. This means using List<T> everywhere,
nullable types everywhere (an act of faith that these will be adequate
by final release) and somehow hooking it into the build system in such
a way that, hopefully, we don't require two separate extra project
files and to rebuild the whole thing twice just to pick up the
generated code.
- Produce
a release of NRobot to include the new security code, and announce it
in enough places that perhaps some people will try producing robot
implementations...
- Watch all three LotR extended editions, especially RotK which I've never seen even the standard edition of.
- Continue to push the nullable type issue with Microsoft any way I can find.
- Learn as much as possible about Visual Studio 2005 and how the
migration will impact cmScribe. I think that actually doing a migration
will take longer than the few days I have, but hopefully I can at least
figure out what the biggest issues will be.
- Catch up on DVR'd TV shows that I haven't watched yet.
- Oh yeah, recover from the surgery...
|
|
"Possible tumor"4/21/2005 Well, yesterday I went for an ultrasound. While it was being taken, the
person taking it told me that the results would be sent to my doctor
"by late tomorrow". So when the radiologist told me that they'd been
sent before I even left the office, I had a strong suspicion that
something serious was wrong.
So I called the doctor's office as soon as I got home and the conversation went a little bit like this:
"Hi, I just had an ultrasound and I was told they'd sent you the results."
"You just had it today?"
"Yes."
"Well, they don't usually send in the results that soon."
"I know that, but they told me they'd sent them in."
I think she got the hint from my tone of voice with that last comment
and went to look. Then the remainder of the conversation went a little
bit like this:
"Yes, your results came back ABnormal, which means it could be an
infection, or it could be a tumor or a mass. They'll need to do a
biopsy. You'll need to come and see the doctor tomorrow. What time is
good for you?"
"Is 7pm okay?"
"Okay, and this is very important, so don't miss that appointment."
<click>
Thanks for the wealth of detail, bitch.
Soo... at 7pm today I'll go and find out what happens next. Since I
wasn't given any instructions not to eat or anything, I doubt they're
doing the biopsy today, which means that all they're going to do today
is probably to talk to me about what they're going to some other time.
Which leaves me wondering... why waste time having me go in today at
all? Why couldn't they just tell me whatever they're going to tell me
over the phone, so that we can get on with the actual business of
getting the biopsy done and finding out whether I'm going to die or not?
My coping strategy in stressful situations like this is to focus on
something comparatively inconsequential and enjoyable. On previous
occasions I've been known to throw myself into reading books (this has
limited success because I finish a typical book in about 3-4 hours) or
programming. Yesterday I should probably have thrown myself into
working, but somehow I didn't have the motivation for that (I wonder
why?). Instead I threw myself into my mini-campaign to get nullable
types fixed in C# 2.0, posting that mammoth blog posting (which I'd
already written the bulk of, but needed some reformatting and lookup of
links) and making sure that I linked to it[1] from everywhere I could
think of - especially Cyrus's "please give me feedback" blog posting,
since he'd already promised to forward my concerns to the language
design team. Actually, if it ends up making a difference, the time
spent doing this will result long term in my company avoiding way more
in lost productivity due to bugs than the few hours I spent doing it.
Today I'm going to be back in work. I'm already running an hour late
getting there in the first place (I'm writing this post on the train),
and I need to leave early tonight to make sure I make that appointment.
And I have no idea how productive I'm going to be while I am there.
I'll do my best, and hopefully they'll understand why I may be a little
distracted.
[1] Actually I managed to screw up the link and link to an
internal-only URL all over the place. Fortunately I got at least one
link right and someone at microsoft corrected all the rest for me.
Needless to say, "D'oh!".
|
|
How to completely screw up a good idea: Nullable types in C# 2.04/19/2005 (Update way too many months later: the final release of 2.0 fixed most (but not quite all) of these issues. Eventually I'll get around to producing an updated table to show how things improved and what didn't. Sorry to everyone at MS who worked on this for not making this update sooner)
So Visual Studio 2005, aka Whidbey, Beta 2 has finally been released,
so I guess this is as good a time as any to rant about my biggest pet
peeve in the new release. For the record, on the whole I'm thrilled and
excited and can't wait for my (3.5Gb!) download to finish to start
working with it (and for Mono to catch up with the new features so I
can use them in Free Software too). C# is hands down the nicest
language I've ever worked with and the 2.0 release takes a good thing
and makes it even better.
EXCEPT for one of the new features.
When I first heard that C# 2.0 would have nullable types built into the
language, I was delighted. The lack of built-in support for nullable
types was one of the very first downsides I ever encountered in the
language, and one of my first tasks when porting nrdo from Java to C#
was to attempt to rectify this lack, at least for the datatypes that
nrdo supports. Fortunately C# is expressive enough that I was able to
implement Nint, Nbool, NDateTime, etc classes which get reasonably
close to providing the desired behavior. There have always been some
limits to how close I could get to the ideal, though: there are some
scenarios where you simply need some help at the language level. So
with C# 2.0 we finally get that help, right?
You can guess the answer. No, we don't.
In fact, Microsoft's team of language designers (who as we've already
established are pretty good, having produced such a kickass language in
the first place) have managed to come up with something worse than what I was able to put together without any compiler changes using the language that they invented, over two years ago.
Let's review the behavior you want from a nullable types feature. It's pretty simple -- in fact, it's already there
in the language. Reference types (classes, interfaces, delegates, etc)
are already nullable. The whole concept of nullability and how it
should behave is already established. The goal is, or should be, to
capture that concept and allow it to apply to value types (structs,
enums, and the builtin types like int and bool, which are actually
structs under the hood) with as few changes as possible to programmer
expectations. Bonus points if you can micro-optimize for
close-to-the-metal performance, but this is a secondary concern.
(Some people may argue that point about performance. To me, it's
self-evident: every use case I've ever seen given for nullable types
has a pre-existing bottleneck in disk or network IO, so a few extra CPU
cycles handling the nullability is going to make absolutely zero
difference in practice. This is a moot point, however: I'll show later
that it's perfectly possible to get the behavior right without any
performance penalty.)
The goal of capturing the existing behavior of reference types and
applying it to value types is pretty much sufficient to define exactly
how an ideal nullable type feature should work, because for any given
code construct using nullable types, you can just subsitute in an
existing reference type and specify that the behavior should be the
same. Here are some examples using ints and strings.
| Reference type |
Nullable type (ideal) |
Nint |
C# 2.0 |
Comments |
| string x = "hello"; |
int? x = 1; |
Nint x = 1; |
int? x = 1; |
Pretty straightforward so far. |
| string n = null; |
int? n = null; |
Nint n = null; |
int? n = null; |
It's hardly nullable if you
can't put null in it. |
| if (n == null)... |
if (n == null)... |
if (n == null)... |
if (n == null)... |
Again hard to get wrong. |
| x.ToString() |
x.ToString() |
x.ToString() |
x.ToString() |
2.0 and
Nint both cheat here by providing ToString() methods that call
ToString() on the underlying int. A tie - it's cheating, but it works. |
| n.ToString() throws NullReferenceException |
n.ToString() throws NullReferenceException |
n.ToString() throws NullReferenceException |
n.ToString() returns an undefined value |
Since 2.0 doesn't really store null as null, it
ends up invoking ToString() on an undefined value. No exception, just a
meaningless result. In my book, invoking a method on null is the
definition of what should be a NullReferenceException. Advantage Nint, but admittedly this isn't a terribly big deal. |
| object o1 = x; |
object o1 = x; |
object o1 = x; |
object o1 = x; |
This looks like it
worked... |
| object o2 = n; |
object o2 = n; |
object o2 = n; |
object o2 = n; |
And so does this... |
| if (o1 == null) is false |
if (o1 == null) is false |
if (o1 == null) is false |
if (o1 == null) is false |
And so does this... |
| if (o2 == null) is true |
if (o2 == null) is true |
if (o2 == null) is true |
if (o2 == null) is FALSE? |
Nint got this right, but C# 2.0 inexplicably thinks that returning
false is a good idea here. If you understand the implementation,
there's a perfectly good explanation for why this happens, but a low
level explanation isn't an excuse for the language flat-out lying to
me. In order to make this work right we need to change the last few
lines... |
| object o3 = x; |
object o3 = x; |
object o3 = x; |
object o3 = Nullable.ToObject(x); |
Holy verbosity batman! And not even a
warning if we forget to do this, which we certainly will most of the time. |
| object o4 = n; |
object o4 = n; |
object o4 = n; |
object o4 = Nullable.ToObject(n); |
Okay, so once we've got them into
objects, we can easily cast them back, right...? |
| string s = (string) o3; |
int? s = (int?) o3; |
Nint s = (Nint) o3; |
int? s = Nullable.FromObject<int>(o3); |
As if the ToObject line wasn't bad
enough, now we have to remember to stick <int> in there at the
right place for no obvious reason. Actually, the natural approach with
casting would work as long as we didn't use ToObject and lived with a
null that isn't actually null. But if you want sane behavior, you need
this insane syntax. |
string s1 = (string) o1; string s2 = (string) o3; |
int s1 = (int) o1; int s2 = (int) o3; |
int s1 = (int) (Nint) o1; int s2 = (int) (Nint) o3; |
int s1 = (int) (int?) o1; int s2 = (int) o3; |
2.0 actually gets this one right for o3, but only because we had to jump through hoops to create o3 in the
first place. With Nint (and 2.0 if you forget to call ToObject) you have to perform this bizarre double-cast
because the value in o3 is actually not an int. In theory,
advantage 2.0; in practice it's a wash because you can't get to the
correct behaviour without working around the wrong behavior first (and actually, Nint has ToObject and FromObject methods as well, but nobody ever calls them - with null behaving correctly, it turns out to be easier not to bother and just use the double-cast when necessary).
Notice that by this point neither version is matching the ideal. |
| string y = t ? "hello" : null; |
int? y = t ? 1 : null; |
Nint y = t ? 1 : (Nint) null; |
int? y = t ? 1 : (int?) null; |
This one is truly a genuine
tie. It's an extremely common construct when using nullable types and
it's a huge pain to always have to remember the cast - I can tell you
this from bitter experience. This is one of the most obvious places
where the language could have helped us all out, but they didn't bother. |
| IComparable c = x; |
IComparable c = x; |
IComparable c = x; |
IComparable c = x.Value; |
Nint cheated
here - because the underlying type is hardcoded I didn't need to do any
magic to implement the same set of interfaces. Still, it gives the
right result. 2.0 makes no attempt to even bother. |
| s.ToString(fmt); |
DateTime? dt; dt.ToShortDateString(); |
NDateTime dt; dt.ToShortDateString(); |
DateTime? dt; dt.Value.ToShortDateString(); |
Again, my implementation cheated but
got the right result; again, 2.0 doesn't bother.
|
Hopefully it's becoming clear by this point that although Nint,
NDateTime and company have serious limitations compared to the ideal,
the behavior of 2.0 is significantly worse. So what were the creators
of 2.0 thinking? These are clearly smart people, how did they get it so
badly wrong?
Well, I can only speculate, but my guess based on their public statements is that they made two fatal mistakes:
- Deciding on an implementation first and then fitting the behavior
to that implementation, rather than designing the behavior first and
then finding a way to implement it.
- Treating performance as if it were the critical factor and
correctness and intuitiveness were entirely subservient to that
overriding need.
It seems to me that the most likely route they took to the current
state is by first making the decision that, "for performance reasons",
the Nullable<T> type must be a value type. Once they'd
made that decision they designed the entire behavior around what was
easiest and most natural to do with value types, and never even thought
to try to match the behavior of reference types.
The most bitter irony is that in fact it would have been perfectly
possible to get the right behavior while still keeping the performance
of a value type. If they'd provided a custom attribute which allowed a
value type to override the standard "boxing" behavior, all the right
behaviors would naturally fall into place. The problem wasn't with the
fact that they decided to use a value type, but rather that they made
that decision too soon and let it drive their design.
I, and others, have reported these issues to Microsoft in their Product Feedback Center. Here are some links:
Notice in particular Microsoft's response to the last one as an
exercise in missing the point. In response to the complaint that using
"(int?)null" is hard to remember, confusing, and needlessly verbose
when compared to just "null", what did they suggest as an alternative?
"default(int?)"! At least my suggestion has null in it somewhere.
And having the casts to and from object work right would be "confusing"
because it would be different from the way other value types work.
Earth to Microsoft: nobody understands how value types work. Nobody cares. The reason the language did such a great job in general is because you don't have to care, because it just works the way you'd expect. This doesn't -- not even close -- and that's what's confusing.
In retrospect I should have probably argued harder about these issues
when I first filed or discovered them. My style of argument is usually
to simply try to get people to realize the manifestly obvious rightness
of what I'm saying ( ;) ), rather than attempt to use any credentials
of my own, because I don't really have any that would impress the
world's largest software company. So when I saw that response that
missed the point so utterly, I gave up -- if they couldn't see the
manifestly obvious wrongness of their own position, they'd
never see the rightness of mine. But what never occurred to me is that
in this area I actually do have some fairly unique credentials - I've implemented,
worked with, and led a team of 4-5 people using, my own implementation
of nullable types over several years. When I say "people will find this
confusing", it's not just a guess: I've actually presented it to people
and seen them find it confusing.
Unfortunately, it'll probably never get fixed now. Backward
compatibility and all that. I'm trying to point Microsoft people to this blog entry in hopes that some of the worst misfeatures might still get fixed; one example is here, and Cyrus has responded by forwarding my issues to the language design team. There may yet be hope...
(oh, and you can find the code to Nint and my other nullable type wrappers here).
|
|
Goo-tri-Matic redux4/8/2005 When writing my previous post,
I wondered whether the paragraph I quoted was an intentional HHTG
homage or just the same idea invented independently. I just realized
that if the reference was intentional, they embedded an even more subtle hint, because DNA can stand for Douglas Noel Adams as well as Deoxyribonucleic acid.
|
|
Goo-tri-Matic?4/1/2005
From the "Google Gulp" home page:
Think a DNA scanner embedded in the lip of your bottle reading all 3 gigabytes
of your base pair genetic data in a fraction of a second, fine-tuning your
individual hormonal cocktail in real time using our patented Auto-Drink⢠technology,
and slamming a truckload of electrolytic neurotransmitter smart-drug stimulants
past the blood-brain barrier to achieve maximum optimization of your soon-to-be-grateful
cerebral cortex.
However, no one knows quite why it does this because it invariably
delivers a cupful of liquid that is almost, but not quite, entirely
unlike tea.
|
|