Thursday, November 12th, 2009
In the November 2009 CTP of "Oslo", we've got some new DSL features in "M", three of which I couldn't wait for the PDC to brag about.

Notice the following in this screenshot of Intellipad:
The use of an expression on the right-hand side to calculate the full name from the first and last name from the parsed input: Name => f + " " + l ("M" supports a rich expression syntax for a variety of data types).
The use of ToInteger32 to provide output data conversion of the Age token as different from the default of string (there's one of these functions for each of the supported "M" data types).
The orange highlighted text in the left-hand pane shows a breakpoint and notice that Intellipad knows that "yours old." is a single thing, so highlights the entire phrase. Also notice the "notification" pane on the far right to show what the data is that's being parsed right now. This indicates that Intellipad is now in DSL debugging mode and lets you step through your input data and debug your language.
You'll also notice richer error information in the Error List at the bottom of the Intellipad window, but that I could've waited to tell you about. : )
All of these "M" features and more can be yours for the low low price of absolutely nothing in the latest CTP of "Oslo", which you can download on November 17th, the first day of the PDC.
Thursday, July 6th, 2006
I had a very unpleasant afternoon trying to get NUnit working, so I thought I'd share my troubles and my solutions (such as they are). A search revealed a lot of folks with the same troubles, but precious few explanations or solutions. The NUnit Quick Start documentation does a wonderful job telling you how to write a simple TestFixture class with one or more Test methods (and even an optional SetUp method). However, as far as I could tell, there wasn't any part of that tutorial that said "Hey! Add a reference to 'nunit.core.dll' as well as 'nunit.framework.dll' to your project or neither nunit-gui nor nunit-console will work;" you'll get a System.IO.FileNotFoundException when you don't have nunit.core.dll (the Exceptions Details menu item doesn't help):

Also, when you start up nunit-console, you better darn well be in the current working directory of the test assembly as well as nunit.core or you're going to get the same message in the console. Likewise, if you start nunit-gui. Further, if you just run nunit-gui, create a new project and then add an assembly, you better have known to
save the .nunit project file in the same directory as the assemblies you add, or you'll be getting the same message again.
And, as if that weren't enough, you'll get this message again if you add nunit integration as an external tool to VS05 (via the Tools | External Tools menu item) using the instructions in the docs. The docs say to use $(TargetPath) and $(TargetDir), but those variables expand to the obj directory on my machine (although that seems wrong to me), not the bin directory, and the obj directory doesn't contain the referenced assemblies.
I never was able to get a VS05 external NUnit tool to work.
Luckily, it's very cool that nunit-gui doesn't keep test assemblies locked and notices when an assembly has changed from underneath it, so that once I do get it started with the appropriate working directory, it works nicely.
As it turned out, there were a lot of ways to get that FileNotFoundException message and I'm pretty sure I found them all before finding any ways to actually make nunit work. None of these things are NUnit's fault – it's damn hard to do dynamic assembly loading in .NET – but it's still on you to make sure NUnit is configured properly.
Finally, the tutorial
shows but doesn't say that you better make your test methods public. Since NUnit uses Reflection, it doesn't need the classes or the methods to be public, but I guess they decided that was an interesting knob to let the developer turn. I'd have preferred to let the default permissions work (internal) to save myself typing, but that's just a nit.
Friday, May 19th, 2006
Years ago, Tim Ewald made an off-handed comment about how cool the real book printing process was and that I should take the boys to see it if I ever got the chance. Two weeks ago, we got the change and this is what we saw.
The printer for Windows Forms 2.0 Programming was Edwards Brothers, Inc. in Ann Arbor, MI. The boys and I took the 8pm flight from Portland, OR to Vegas, then the 11pm flight to Detroit, arriving at 6am, having gotten about 3 hours of sleep during the flight (the boys first redeye!). On the way out the door, we'd been a little rushed (I was trying to finish off the flooring in the new laundry room so that Melissa could have her washer and drier back after its long absence during the renovations), so had left all of the carefully prepared contact info and directions sitting in my office, meaning that the first thing we had to do was to sit in the airport near a hotspot and download driving directions. This and a trip to the restroom for a teeth brushing and a fresh shirt and we had caught our 2nd wind.
We picked up our car and had a very pleasant trip to Ann Arbor, where we stopped at Mike's Coney Island for pancakes and omelets (I had the pancakes and my youngest surprised the hell out of me by ordering an omelet -- they grow up so fast!). We arrived at Edwards Brothers around 7:30a (in some time zone) and learned that they had been in business for a little while longer than Sells Brothers, Inc:

Unfortunately, we'd arrived before our escort, the very lovely Ms. Cindy Rohraff, so she caught us napping in the parking lot around 8am. Still, as we entered the lobby, it was clear that they were as excited to show us around their operation as we were to be there:

After Cindy purchased caffeinated sodas (Microsoft has spoiled me enough that I hadn't come prepared to actually *purchase* sodas...), we began our tour and the first thing we saw was a big basket of the rejected bits of my book:

As it turns out, these bits weren't rejected on content, but rather on printing quality. <whew>
The size of the printing floor is enormous. It felt like 4+ football fields in there and I'm sure I saw at least 50 different books in various stages of production. In this picture of the boys and Daryl (in charge of printing our job), you can get some small sense of the size of the place:

For all those jobs, Edwards Brothers employees are constantly throwing away covers, inserts and "signatures" (bundles of pages that get stuck together to produce a book) that don't meet their quality standards. In fact, I saw one guy throwing away what must've been 100 freshly printed book covers because he'd examined one of them with a magnifying glass and found a flaw. These guys are serious.
Next, we found our way to the input end of the printer, where they feed in 2-ton rolls of paper:

If you look closely, you'll see that their are two rolls of paper connected here. Only one is fed through at a time, but when the first is out, the 2nd is spliced in automatically without ever slowing down the printing process, providing a virtual infinitely long roll of paper. A cache of paper is kept on rollers to enable the time it takes to splice:

When the splice happens, the rollers at the top and the bottom (not shown) get close together as the cache is used up and then after the splice, the rollers expand again as the cache fills up. That's not to say that the printer doesn't stop at all. When it's running, the paper goes through the process so fast, the words and images are a blur, but whenever there's a problem, the process grinds to a halt and restarts w/o issue.
After the cache, the paper feeds into the printing machine (or machines, depending on how many colors the output needs):

and comes out all printed:

The printing itself happens with ink that's been spread over thin metal plates containing the laser-etched images of the book in 48 page sets, one plate for each side. The paper goes through the printer between the two plates. Here you can see Daryl changing the plates:

To support the ability to change the plates, the printer opens in the middle with hand cranks, which Daryl let the boys operate:

Daryl was one of the long list of people that bent over backwards to show us around and explain to us the process (Thanks, Daryl!). The plates themselves are shown here:

After the pages are printed, they're rolled and cooled to set the ink:

After the ink is set, the paper goes into a machine that cuts it and turns it along three separate paths (you can see one of the paths continue over the top of the cutter):

Each path of paper is routed through a folding and bundling machine:

Once out of the bundling machine, the bundles are gathered together:

compressed (losslessly):

and stacked on pallets in sections (my book had 27 sections) for transport to the bundling department:

In my case, the cover:

and the color insert:

were printed by another vendor, but Edwards Brothers is fully capable of printing these elements. In fact, my book is pretty trivial compared to the tricks that the EB folks can do. I think next time, I'll ask for a two-part cloth-bound hardcover with stained edges just to test 'em. : )
All 6500 copies were printed in one day and stacked over in the binding section of the printing plant (and again, notice the size of this place):

All of the sections were then stacked into the collating machine:

After this, my batteries ran out of power temporarily (they helpful staff at EB replaced 'em for me), so I can't show you pictures, but once the books are collated and the spines bathed in glue at two temperatures (275 and 300 degrees Fahrenheit), the covers were attached and crimped and the books given "the wheel treatment" as they exited the machine on their way to the cutter:

The cutter was this very scary machine they called "the guillotine" and was able to cut all three edges of my 1000 page book in a single slice and then stack the books into neat piles:

Not all the books survive the trip through the cutter (much like a real guillotine):

Once the books have been trimmed, they're boxed and put on a conveyer to the shipping department. The shippers are responsible for lifting every single book (I apologized for my wordiness) and putting them onto palettes that are wrapped in plastic and hoisted by forklift:

The forklift stacks each palette in the warehouse for shipping:

That's not all. The little bits of paper that were the edges of my book and the low quality copies are shredded and compressed into giant chunks:

Like the books, the cubes of paper are stacked in the warehouse for shipping:

The waste paper is shipped off to a recycler for use in other books, whereas the books themselves are shipped to distributors, like Amazon, Borders and Barnes & Noble. By now, the first of the pre-orders should be being fulfilled. The ones that aren't sent off for sale are given to vain authors that need a physical manifestation of their work to really understand the amount of time and energy that goes into producing their book:

Special thanks go to Cindy, who was our most gracious hostess and to the staff and management of Edwards Brothers, Inc, for their work in producing such a high quality product. I also have to thank the boys for their infinite patience in indulging their father traveling to the middle west of our country to hang out amongst the stacks and stacks of books in progress. And, of course, thanks go to the readers. I hope you enjoy reader the book as much as I enjoyed working on it.
Monday, October 24th, 2005
I was playing around with a state machine workflow in WF and ran across something that I didn't except. Apparently if you have a an activity that can be other than a singleton, e.g. inside of a loop or as part of a state machine workflow, then an activity can not be accessed by it's workflow instance variable. For example, the following works just fine in a sequence workflow:
public partial class Workflow2 : SequentialWorkflow { private void code1_ExecuteCode(object sender, EventArgs e) { // Retrieve Comment property from typed event sink (generated by wca) string comment = this.eventSink1.Comment; Console.WriteLine("Order approved w/ comment= {0}", comment); } }
However, in the case where the activity can be executed more than once, it's my understand that each time through, there's a new activity object that's different from the named activity associated with the workflow. To get a hold of the current activity, you have to traverse the activity tree, which you can access in a code activity via the sender's parent property:
public partial class StateMachine1 : StateMachineWorkflow { void code2_ExecuteCode(object sender, EventArgs e) { // Retrieve Comment property from typed event sink (generated by wca) Code codeActivity = (Code)sender; IOrderService_Events.OrderApproved eventSink2 = (IOrderService_Events.OrderApproved)(codeActivity.Parent.Activities["eventSink1"]); string comment = eventSink2.Comment; // eventSink1.Comment will be unbound Console.WriteLine("order approved w/ comment= {0}", comment); } }
I haven't figured out whether this makes sense to me yet, but that's how it is. You can get the code here.
Monday, October 24th, 2005
If you downloaded the samples from my previous two forays into WF communications programming, you probably couldn't get them to build. The problem is that when I packaged things up, I removed the bin directories. I haven't figured out just why this causes everything to stop building, but with the help of Dennis Pilarinos, I rearranged the two projects into three:
communications interface (CommunicationLibrary): the data exchange service interface definition, compiles into CommunicationLibrary.dll
communications activities (WorkflowProject1): uses a pre-build step to generate the typed activities generated from the communications library using the wca tool, references CommunicationLibrary.dll and compiles into WorkflowProject1.dll (nice name, eh? : )
application (EventSinkAndMethodInvoke2): the app that uses the typed activities, references both CommunicationLibrary.dll and WorkflowProject1.dll
It's only by separating the communications activities into a separate assembly that things can build and rebuild reliably. You can download the newly configured code here. Enjoy.
Sunday, October 16th, 2005
After complaining about the inability to bind directly to event inputs, Dennis suggested I try the Workflow Communications Activity generator utility (wca.exe). If you point it at an assembly that contains interfaces decorated with the DataExchangeService attribute, it will generate typed invoke method and event sink activities for each method and event in the interface. To support this, I moved the communication interface, IOrderService, and types the interface depends on, i.e. Order and OrderEventArgs, to a separate library project and added the following post-build step:
"C:\Program Files\Microsoft SDks\Windows Workflow Foundation\wca.exe" "$(TargetPath)" /o:"$(SolutionDir)\EventSinkAndMethodInvoke2"
The reason I added this as a post-build step in the library project instead of as a pre-build stuff in the actual wf app, is because I want to have the types to program against whenever the library changes. However, either way, to get the new activities to show up on the toolbar, you have to build the application. Once I'd done that, I updated my workflow using the typed activities:

Unfortunately, just as I was capturing that screenshot, the Workflow Designer couldn't find the activities, so it dropped them from my workflow without so much as a "by your leave," reminding me where much of the early WinForms Designer.
However, two nice things result from these generated types. The first is that my design-time experience, either in the designer or in the XOML, is improved because I don't have to do a bunch of parameter binding:
<SequentialWorkflow
x:Class="EventSinkAndMethodInvoke2.Workflow2"
x:CompileWith="Workflow2.xoml.cs"
ID="Workflow2"
xmlns:x="Definition"
xmlns="Activities">
<ns0:CreateOrder
customer="Fabrikam"
orderDescription="42" Plasma TV"
ID="createOrder1"
MethodName="CreateOrder"
InterfaceType="EventSinkAndMethodInvoke2.IOrderService"
xmlns:ns0="IOrderService_Operations" />
<ns1:OrderApproved
ID="orderApproved1"
EventName="OrderApproved"
InterfaceType="EventSinkAndMethodInvoke2.IOrderService"
xmlns:ns1="IOrderService_Events" />
<Code ExecuteCode="code1_ExecuteCode" ID="code1" />
<Terminate
Error="*d2p1:ActivityBind(ID={orderApproved1};Path=Comment)"
ID="terminate1"
xmlns:d2p1="ComponentModel" />
</SequentialWorkflow>The other nice thing is that, because the typed event sink has properties that directly expose the event arguments, i.e. Comment and Order, instead of just via parameter bindings, I can bind to them in the b1 build of WF. This reduces my coupling, because the terminate activity doesn't know where it's getting it's input, and it takes away that "global variable" feel I had when I was binding parameters in and out of fields on the workflow itself. If I want to access the event sink's typed properties directly, I can do so, as shown in the code activity's handler:
public partial class Workflow2 : SequentialWorkflow {
void code1_ExecuteCode(object sender, EventArgs e) {
Console.WriteLine("Order: approved w/ comment= {0}", orderApproved1.Comment);
}
}I really like that typed activities are generated for me based on my data exchange service interfaces, but it's still a manual, multi-stage process today. I'd prefer that it happened automatically, like typed resources and settings in VS05. If that can't happen, I'd prefer to be able to bind directly to the parameter binding list that the generic event sink activity already knows about. At least that way, if there's a problem, my workflow doesn't get destroyed because of it.
The updated VS05b2, WF/WinFXb1 sample code is available for your enjoyment.
Friday, October 14th, 2005
I just an interesting week taking a crash course in Windows Workflow Foundation (formerly WWF, not just WF) programming. My big hang-up was, how do I communicate into, out of and between activities?
I started with Dennis's explanation of communications into and out of a workflow. The fundamental idea is that, once a workflow has started, you can't talk to it directly. Instead, you set up a communications interface that the workflow can use to talk to the host and that the workflow can use to watch for events from the host, e.g.
[DataExchangeService]
public interface IOrderService {
void CreateOrder(string customer, string orderDescription);
event EventHandler<OrderEventArgs> OrderApproved;
}The interface is marked with the DataExchangeServer attribute to mark it as an interface suitable for host<->workflow communication, but other than that, it's a normal .NET interface. The host, i.e. the chunk of code that creates the WF runtime, implements data exchange service interfaces as singletons, e.g.
class OrderServiceImpl : IOrderService {
Dictionary<Guid, Order> _workflowOrderMap = new Dictionary<Guid, Order>();
public void CreateOrder(string customer, string desc) {
_workflowOrderMap.Add(BatchEnvironment.CurrentInstanceId, new Order(customer, desc));
}
public void ApproveOrder(WorkflowInstance wf, string comment) {
if (OrderApproved != null) {
Guid wfId = wf.InstanceId;
OrderApproved(null, new OrderEventArgs(wfId, _workflowOrderMap[wfId], comment));
}
}
public event EventHandler<OrderEventArgs> OrderApproved;
}With this implementation, the host is allowing the workflow to call the CreateOrder method (which we'll see it do later) and to subscribe to the OrderApproved event. The CreateOrder method uses its arguments to create an Order object and associate it with the ID of the currently executing workflow (available via BatchEnvironment.CurrentInstanceId). Remember, the service implementation is a singleton, but any number of workflows can call it, so when they do, we track information on a per workflow basis.
WF Question #1: How do I associate objects directly w/ a running workflow instead of tracking things in dictionaries?
The OrderApproved event is used to get information into the workflow.
In our scenario, imagine we're creating an order, approving it (w/ a comment) and logging the result. In my sample, I have a workflow imaginatively named "Workflow2" which captures this sequence:

The invoke method activity is bound to the CreateOrder method of the IOrderMethod method:
<InvokeMethodActivity
ID="invokeMethodActivity1"
MethodName="CreateOrder"
InterfaceType="EventSinkAndMethodInvoke2.IOrderService">
<InvokeMethodActivity.ParameterBindings>
<wcm:ParameterBinding ParameterName="customer" xmlns:wcm="ComponentModel">
<wcm:ParameterBinding.Value>
<?Mapping XmlNamespace="System" ClrNamespace="System" Assembly="mscorlib" ?>
<ns0:String xmlns:ns0="System">Fabrikam</ns0:String>
</wcm:ParameterBinding.Value>
</wcm:ParameterBinding>
<wcm:ParameterBinding ParameterName="orderDescription" xmlns:wcm="ComponentModel">
<wcm:ParameterBinding.Value>
<?Mapping XmlNamespace="System" ClrNamespace="System" Assembly="mscorlib" ?>
<ns0:String xmlns:ns0="System">42" Plasma TV</ns0:String>
</wcm:ParameterBinding.Value>
</wcm:ParameterBinding>
</InvokeMethodActivity.ParameterBindings>
</InvokeMethodActivity>In this case, we're hard-coding the custom and order description fields, but in a real workflow, you'd take those as input parameters.
WF Question #2: How do you pass input parameters into a workflow?
WF Question #3: Is it legal XML to have a processing instruction in the middle of a file, e.g. <?Mapping...?>?
After the workflow creates the order, it waits for a human to approve it via an event sink activity:
<EventSinkActivity
ID="eventSinkActivity1"
EventName="OrderApproved"
InterfaceType="EventSinkAndMethodInvoke2.IOrderService">
<EventSinkActivity.ParameterBindings>
<wcm:ParameterBinding ParameterName="Comment" xmlns:wcm="ComponentModel">
...
</wcm:ParameterBinding>
<wcm:ParameterBinding ParameterName="Order" xmlns:wcm="ComponentModel">
...
</wcm:ParameterBinding>
</EventSinkActivity.ParameterBindings>
</EventSinkActivity>The event sink waits for the host to fire an event, which has several interesting bits. The first interesting bit is the parameter names, which are bound to the public properties of the OrderEventArgs class passed to the OrderEvent event:
[Serializable]
public class OrderEventArgs : WorkflowMessageEventArgs {
Order _order;
public Order Order { get { return _order; } }
string _comment;
public string Comment { get { return _comment; } }
public OrderEventArgs(Guid workflowInstanceId, Order order, string comment)
: base(workflowInstanceId) {
_order = order;
_comment = comment;
}
}Notice that the custom OrderEventArgs class derives from the WorkflowMessageEventArgs class and passes in the workflow instance ID. This is required so that the event can be routed to the appropriate workflow. Without it, you'll get the following illuminating error in beta 1:
"An unhandled exception of type 'System.Workflow.Runtime.EventDeliveryFailedException' occurred System.Workflow.Runtime.dll"
WF Question #4: Can we get more descriptive exception messages?
Luckily, this error only happens when you're running under the debugger; it's swallowed completely when your program runs normally.
WF Question #5: Can we get exceptions at runtime, too?
Notice also that the OrderEventArgs class is marked with the Serializable attribute. This is required to cross the boundary into the workflow. Without it, you'll get the ever helpful EventDeliveryFailedException exception.
WF Question #6: What boundary are we crossing when fire an event into a workflow?
Further, all objects sent into a workflow need to be serializable as well, like the Order class (also yielding EventDeliveryFailedException if you forget):
[Serializable]
public class Order {
Guid _orderId = Guid.NewGuid();
public Guid OrderId { get { return _orderId; } }
string _customer;
public string Customer {
get { return _customer; }
set { _customer = value; }
}
string _desc;
public string Description {
get { return _desc; }
set { _desc = value; }
}
public Order(string customer, string desc) {
_customer = customer;
_desc = desc;
}
}Firing the event is as easy as calling our helper function from outside of our workflow to cross the boundary into the workflow:
class OrderServiceImpl : IOrderService {
...
public void ApproveOrder(WorkflowInstance wf, string comment) {
if (OrderApproved != null) {
Guid wfId = wf.InstanceId;
OrderApproved(null, new OrderEventArgs(wfId, _workflowOrderMap[wfId], comment));
}
}
public event EventHandler<OrderEventArgs> OrderApproved;
}Notice that we need the workflow instance ID, which we pass in via the WorkflowInstance we get when starting the workflow:
static void Main() {
WorkflowRuntime workflowRuntime = new WorkflowRuntime();
// Add IOrderService implementation
OrderServiceImpl orderService = new OrderServiceImpl();
workflowRuntime.AddService(orderService);
workflowRuntime.StartRuntime();
workflowRuntime.WorkflowCompleted += OnWorkflowCompleted;
Type type = typeof(EventSinkAndMethodInvoke2.Workflow2);
WorkflowInstance wf = workflowRuntime.StartWorkflow(type);
// Simulate human decision time and approve the order
System.Threading.Thread.Sleep(1000);
orderService.ApproveOrder(wf, "this is a *fine* order!");
waitHandle.WaitOne();
workflowRuntime.StopRuntime();
}Once the event data is fired into the event, the event sink's parameter binding provide enough infrastructure to be able to access the data in subsequent activities, e.g. the code activity that comes right after the event activity:
<Code ExecuteCode="code1_ExecuteCode" ID="code1" />
In the code1_ExecuteCode method, my code can reach over to the event sink activity's parameter bindings and access the data that was fired into it:
public partial class Workflow2 : SequentialWorkflow {
void code1_ExecuteCode(object sender, EventArgs e) {
Console.WriteLine("Order: approved w/ comment= {0}",
eventSinkActivity1.ParameterBindings["Comment"].Value);
}There are three reasons I really don't like this code. The first is that I have to cast to get something typed out of the Value property and I don't like casting. The second reason is that this technique only works through code; I can't bind to a parameter from an event sink to a property on another activity, e.g. the Error property on a Termination activity. The third, and most damning reason, is because this induces coupling from my code activity to my event sink activity. I don't want this coupling to be captured in code, which is another reason to really like the declarative data binding solution.
WF Question #7: Why can't I bind event sink parameters as input into other activities?
Unfortunately, while I can't solve reasons #2 or #3 very well, I can solve them partially and I can solve #1 nicely by adding some fields to my workflow class:
public partial class Workflow2 : SequentialWorkflow {
Order _approvedOrder;
string _approvalComment;
void code1_ExecuteCode(object sender, EventArgs e) {
Console.WriteLine("Order: approved w/ comment= {0}", _approvalComment);
}
}The _approvedOrder and _approvalComment fields can be bound to the event sink parameters like so:
<EventSinkActivity
ID="eventSinkActivity1"
EventName="OrderApproved"
InterfaceType="EventSinkAndMethodInvoke2.IOrderService">
<EventSinkActivity.ParameterBindings>
<wcm:ParameterBinding ParameterName="Comment" xmlns:wcm="ComponentModel">
<wcm:ParameterBinding.Value>
<wcm:ActivityBind Path="_approvalComment" ID="{/Workflow}" />
</wcm:ParameterBinding.Value>
</wcm:ParameterBinding>
<wcm:ParameterBinding ParameterName="Order" xmlns:wcm="ComponentModel">
<wcm:ParameterBinding.Value>
<wcm:ActivityBind Path="_approvedOrder" ID="{/Workflow}" />
</wcm:ParameterBinding.Value>
</wcm:ParameterBinding>
</EventSinkActivity.ParameterBindings>
</EventSinkActivity>Now, when the event sink activity fires, these two workflow fields are populated so that by the time the code activity is executed, they're ready for use, all typed up and ready to go. However, while this reduces coupling to some degree, i.e. the code activity just hopes that somebody provides the data and not that is has to be a specific activity, this is not the same as failing to execute an activity until you have valid values to pass to a function. Instead, it's like setting global variables, hoping they're set properly when the code that accesses them needs them and having no idea what's affected if you have to remove them.
Still, with the parameter binding in place between the event sink parameters and the workplace class fields, I can bind the input to an activity:
<Terminate
Error="*d2p1:ActivityBind(ID={/Workflow};Path=_approvalComment)" ID="terminate1"
xmlns:d2p1="ComponentModel" />Clearly, this is binding syntax only a mother could love, but luckily the designer did it for me, it's going to change in the next beta and it does most of what I want, i.e. bind the input of the Error property on the Terminate activity to something produced by another activity. It's not the same as binding the event sink activity parameters directly, thereby allowing me to shed this dirty global variable feeling, but it's oh so close.
The VS05b2, WF/WinFXb1 sample code is available for your enjoyment.
Props to Dennis Pilarinos and Anandhi Somasekaran from the WF group for help figuring this out.
Thursday, October 6th, 2005
A while has passed since the 2005 PDC, so I can laugh about it now, but at the time, things were not fun at all.
It started long before the PDC. Doug Purdy and I were asked to do the Avalon/Indigo "interop" talk. We got together and I volunteered to "PM" it (PM is a verb as well as a noun at MS). Over lunch (we both love CPK), we hammered out our ideas for a fun demo that showed off the integration points between these two technologies, split the tasks, set the dates and off we went.
My first task was to gather the graphics we'd need for our Avalon front-end, since neither Doug nor I are artists. I asked newly hired Adam Kinney, who did a fabulous job. Everything good about the app is Adam's fault. Thanks, Adam! I was also to build a script for our demo that we could run by folks internally, which I did.
Doug's job was to put up a web service that I could write my app against. The problem is that Doug has far, far more responsibility than I do, since while my day job is to PM a group of about 5 people, Doug's is to PM a group of about 250. At MS, when you've got too much to do, the time you set aside for a lower priority task is often "pimped" to service the higher priority task (that's a technical term : ). I got pimped -- hard (without so much as a kiss...).
When you're pimped at MS, one handy trick is to schedule a "working" meeting, where you sit in the room with the folks that were supposed to do the work on their own and you work "with" them, i.e. look over their shoulder and give them shit if they're not working on your thing. Unfortunately, the only time I could get with Doug was 6pm-10pm one evening, but I took it and we spent a pleasant evening writing our bits of the app and making them work together under WinFX beta 1.
Then the September CTP bits came along that we were to give away at the PDC. I spent a few hours porting my bits, but Doug didn't have the time to do his 'til just before the PDC, so we didn't get a chance to integrate our two apps 'til the week of the PDC itself.
But let's back up. I'm at the PDC Tuesday morning. My Avalon app portion is ported and running, as is Doug's Indigo app portion. I go to the keynote in the Microsoft employee overflow where we get to make fun of the people on the screen w/o disturbing anyone and we're having a good ol' time. I don't see Doug, but that's not surprising, so I head up to the speaker lounge, grab a network connection and drop him a line.
By Wednesday afternoon, I still hadn't heard from him. I drop him another line. Still nothing. Martin Gudgin, my long-time friend and Doug Purdy direct (aka Martin works for Doug), places a call to him for me. Doug picks up. He can do that because he's not on a plane down to LA. No, he wouldn't do that because planes freak him out. Instead, he's on a train. The ride is 35 hours long and he's still in the middle of it. I won't see him 'til later this evening, if at all. At least he's safe and he's using the training ride to polish his demo, so I go to a party and feel better.
I awake to find an S+ in my inbox. "S+" is what what 'softies call an Outlook appointment request because we used to schedule meetings using a program called Schedule Plus and, like many things at MS, the noun was verbitized. The S+ is from Doug and he wants to meet to me this morning to have a working meeting to do our integration. I'm happy to do so, so I report to the speaker's lounge on Wednesday at 10am.
Except for commitments, like Doug's other talks, my Windows Technology Off Topic mailing list member mini-DevCon and a tiny bit of sleeping, Doug and I are working on the integration of our demos from Wednesday morning at 10am until Friday morning at 4am (our talk is at 10:30am Friday). To get things to work, we're battling issues we never saw before because in an effort to give our customers the latest and greatest bits, the CTP has regressed on the Avalon/Indigo integration front.
For example, while the add-in that adds Avalon/Indigo features to VS05 modifies the Add Web Reference to create an Indigo client-side proxy, there is no way to set the flag to give us async methods and the generated code for endpoints with more than one method doesn't actually compile (nor is it easily fixed to compile). The command-line utility, svcutil, generates async methods and the output compiles, but when the async methods are called, they block the UI thread. The worker thread is still spawned and used, mind you, it's just that the UI thread is blocked 'til the worker thread is done (handily defeating the purpose). Even when we switch the client to use BackgroundWorker so that async calls are really async, the first Indigo call takes 20+ seconds. We didn't learn this 'til later, but this was apparently due to a DNS look-up error when Indigo was used with a network connection (we were using two machines to be as real-world as possible...). This list went on and on.
We did finally get it all working by 4am, but by then we were swearing at everyone and everything that had gone into the creation of either technology, which made us good company for the other last-minute-Lou's that were hammering away at their demos into the wee hours of the morning (although Doug and I had the dubious honor of being the last ones in that damn speaker lounge).
I woke up early, so I headed to our session early. Doug had another talk right before ours, so I thought I'd get things set up, test them out as much as possible, etc. Sitting in the back of the room about 15 minutes before the other talk was about to end, I started my laptop up from Hibernate mode just as my cell phone rang. It was a friend, so before my machine had fully booted, I flipped the lid closed again and answered my phone. Just then, the speaker ended his talk, so I ended my call and wandered to the front of the room with 45 long, juicy minutes to set up.
I sauntered onto the stage. I got out my power cord in a leisurely fashion and started my laptop to boot.
I hooked up my power cord and thought I saw a hint of blue on my laptop screen and it was booting again.
I watched more carefully and yes, that was the BSOD. I shutdown the power completely and waited a few seconds. Booting again gave me a BSOD again. Minutes are ticking by, eating into my comfortable lead. The IT guys are coming up to test my laptop's video output and to hook up the mic. I ask if someone technical could help me. They get onto their radios with a hint of panic in their voices.
By now, I've trying safe mode booting, both into Windows and into the command prompt, both of which yielded a BSOD. Then I remember my CD case. I always carry a set of CDs with me with my most critical software, just in case (those old instructor habits die hard). I had recently gone through it and cleaned out a bunch of stuff, so the single CD left in the case is my Windows XP SP2 boot CD. I put it in and pray. As it turns out, I've got plenty of time to establish a connection with my maker, because it takes ages for that XP CD boot to get somewhere useful. I chose the Recovery Console and wait.
In the meantime, Martin has wandered in to say hello. It's about 10:10 and my talk starts in 20 minutes. I ask him if he's got WinFX and VS installed on his laptop. He says he's got WinFX and a version of VS installed, but neither are the PDC bits, so they're not likely to help me (I was up 'til 4am getting my bits working for the PDC build -- I couldn't imagine switching versions on the fly). I ask him to find someone else with a machine I can use. In the meantime, I'm checking the PDC boxes. They've got two, both running WinXP (great!), but neither running WinFX or VS05 (boo!). All I can do from these machines is run my PowerPoint deck, but because this session was meant to be code only, we've only got about 3 slides. Without the demo, we've essentially got nothing.
Now it's 10:15 and the audience is gathering. Also gathering are people trying to help. One guy's got a machine with the right bits, but they're running in a VPC, which is not the greatest place to show off Avalon (but thanks, whoever you are, for trying!). One attendee offers his laptop, which has the right bits installed, but he doesn't have a power cord and there's only about 30 minutes worth of battery life left. Rob Relyea (I think) finally comes up with his shuttle PC with the right bits and a power cord and starts hooking it up, along with everyone else I can find that might be able to help me, all of whom are now busy hooking up PCs in line line that nearly bows the table on the stage.
At last, I'm at the recovery console and I'm running every command I can think of, e.g. fixmbr, fixboot, chkdsk, etc. Most of the commands are saying scary things like "Are you sure you want to execute this command? It might work, but I can't promise not to cause flames to shoot out the USB slots." I'm typing "y" as fast as I can 'cuz I've got nothing and at least flames would be engaging.
Now it's 10:20 and Doug has just shown up from his previous talk. He boots his laptop without a problem, the sound guys have their way with him, his video works and he announces that he needs caffeine. Of course, I've got adrenaline shooting out of my eyeballs by now and I want him to feel my pain, so I tell Doug his is absolutely not going anywhere. He says he is. I tell him he's not. Now, Doug and I are both big, loud guys, so all of the folks in the front rows, including representatives from the technologies we're demonstrating that asked us to give this talk, are witnessing us fighting with each other minutes before the talk begins. Finally, Rob to the rescue again, offers to get Doug some coffee.
At around 10:25, all of the fixthis and chkthat commands have been run and I reboot. For the first time in almost an hour, I can log into my laptop and I start to think I might actually get to give my first PDC session ever (did I mention I felt a little pressure?). We pull up the finished version of our app and test it end to end. There's still the initial 20 second lag, but it all works. Brimming with confidence that only comes from avoiding a bullet with your name on it, I hook up the video on my laptop.
All that comes out is scrambled garbage.
Now we're panicked again, frantically futzing with the video settings. They all seem mostly right, so we change some things that shouldn't matter, but nothing fixes the problem.
We can't get the video to work and now it's 10:30.
The sound guy puts on my mic and practically dives off the stage in case something does burst into flames, as seems the logical next event.
I pull up the slides on one of the PDC computers so we can start the talk and reboot my laptop at 10:31.
At 10:32, we've started the talk. Doug is telling folks who we are and why we're here. I'm looking for the backdoor.
I log into my laptop, start up the talk on the same slide that's currently showing and press the switcher to show my laptop's video.
It shows and I am Superman.
This is where things get fun, because at our most morose, Doug and I are loud and obnoxious in a way that most audiences like. By now, we've poured so much energy into the talk right up until literally the very last minute that now we're practically levitating.
Doug has the idea to take questions first, so I'm writing down a list of stuff that has nothing to do with the talk, all the while we're making fun of each other and the audience. The audience eats it up.
I've got a developer from Indigo and a PM from Avalon in the front row that we bring up on stage because they've just gotten engaged and we made them kiss to show off the power of Avalon and Indigo integration, I announce that I got ordained on two separate internet churches the night before in case they wanted me to marry them on the spot. They politely decline, but the audience eats it up.
I show off our application with increasingly funny caricatures of Doug and me. The audience eats it up.
We start into the actual code integrating Avalon and Indigo, joyfully pointing out the problems with the current versions of each other's technologies, making sure the audience knows the current state of the bits and how to work around problems while pledging to fix things by RTM. After a bit of trouble, we get our integration code working across the network between machines and give each other an enormous hug, professing our undying love for each other and guess what? The audience eats it up.
It's like a TV sitcom with a laugh track every 60 seconds, except that Doug and I are just riffing and the laughter is real. I'd say it was the speaker version of jazz and it rocked the house.
Of course, as was inevitable, we got to a place where our demo finally failed, but only after we'd gone over our allotted time by 5 minutes and Doug redeemed us with 10 seconds more typing, an off the cuff demo and a flourish, at which time we wisely took our bows and got the hell off the stage.
What was the result? Lots of folks said stuff in their evals like, "Siegfried and Roy have nothing on Chris and Doug. It was a perfect way to wake up on the Friday of the conference." However, some folks dinged us for "too much show, not enough substance," and they were right. We would've shown more stuff if we could have, but frankly, I'm just happy one of us didn't spontaneously combust.
The best results of the talk was that Doug went home with a giant list of bugs to get fixed before the WinFX RTM and I kicked off the "WinFX Cross-Pillar SDK Technology Sample Working Group" dedicated to making sure we have a much greater set of SDK samples to show off and drive quality into the cross-pillar integration points between Avalon, Indigo and Workflow. I'd actually like to poke my head out of the speaker lounge at the next PDC and I'll be damned if I'm going to let the quality of these particular technologies stop me next time if I can help it (and I can).
Still, I find I have a new appreciation for jazz...
Thursday, September 22, 2005
I am having so much fun, I can't not tell you about it.
After a few months of wallowing, I found out something about myself: I'm really good at digging into the state of the art, whether it's one technology or a feature across technologies, if I have a problem I'm trying to solve. However, if I'm just wandering in a space w/o an explicit goal, e.g. give a presentation, build an app, write an article, I'm lost; I just can't muster any juice. What this means is that when I present my thinking in an area where I'm wandering aimlessly, I'm not married to any of my conclusions. If someone pushes back, I can't really get behind my conclusions because I just don't care. If you're giving a presentation on how you think a certain technology should be used and you don't have a firm opinion one way or the other, that's just death.
So it wasn't pretty for my first couple of months in my new project team. I never felt like I was making any progress because I didn't understand what problem we were trying to solve. How can you know how much further you've got to go if you don't know where you're headed?
So, when my boss walked into my office with an actual problem, I jumped at it. He asked me to understand one of the internal applications that another team built at MS and give a presentation on it. The presentation was to a group of architects from across MS. The idea was to pick something to build that was real and that we could learn from as we attempted to apply what we hoped our product would be when it was done (developing prototype product functionality along the way). The catch: I had 36 hours to prepare the presentation (16 of which would commonly be used for sleeping), there weren't any product architecture docs and nobody knew anyone from that team. I didn't get much sleep, but I did prepare a presentation that amazed the audience given the amount of time I had. Plus, we ended up picking that app and we've been building it all summer, with me writing some of the code and leading the team writing the rest of the code. We've been through two milestones and are in the middle of a third, learning a ton about what we were going to build from solving an actual problem. That learning has fed into a product plan that I'm working on with my boss and that we'll use to capture our team's thoughts and then getting our management and our internal partners on board.
This goes along with me learning how to be a real MS Product PM, giving my PDC talk, organizing a group to fix what I don't like about the technologies that I found out about during the preparation of my PDC talk, working on three books and helping to get Genghis resurrected for .NET 2.0. So, I'm busy doing so many wonderful technical things that I can barely stand it. Wahoo!
Friday, August 12, 2005
I spent last weekend in Reno with Joel, my fraternity brother, wife's sister's husband and business partner (we have an investing business). We'd both been working killer hours lately and we needed a break, so we spent three days on a "dirty boy's weekend" (as another friend called it). We gambled at a casino or two and drank and ate and slept in and took in a showing of The Dukes of Hazzard and, one night, we went to a comedy club. The main act was a fabulous comedian that took his craft very seriously. In fact, he was so committed to what he did, that he went to all the trouble to have his hair cut so that it looked short with it tucked into his hat, even though it was really very long, just so he could whip it out in his act for comedic effect. During his act, he juggled a chainsaw, did an amazing card trick, road a unicycle, played music on the MP3 player he'd plugged into his mic, brought folks up on stage and had us all laughing the entire time. He had this way of bringing folks up on stage where he'd point at a person, ask them their name (e.g. "Bob") and then say, "Folks, give Bob a hand as he comes up to help me out with this next bit." He was a true entertainer.
After he'd had us busting a gut for about an hour, he pointed to me and said, "What's your name?" I answered him and he said, "Folks, give Chris a hand as he comes up to help me out with this next bit" and up on stage I went. Now, from a distance, I look fairly normal sized, especially sitting down. Our comedian was a tad on the short size, frankly, so when I walked up on stage, I towered over him and his eyes got real big. Of course, I'm shy on stage, so I didn't say much, but nodded and played along good-naturedly. I mean, hell, I'd once led an audience into a public pie lynching of a suited marketing person, so I knew the drill.
He looked at me and said, "Chris, I want you to follow along with me. Do what I do. If you don't do what I do, it won't be funny. The funnier it is, the more likely you are to get laid." Of course, I was in Reno w/o my wife, so unless Joel got frisky, I wasn't going to get laid, but that didn't mean I wasn't enthusiastic about increasing my chances (and, of course, the audience was egging me on), so I nodded my head earnestly that I would do my best to follow along. He gave me a floppy cabby hat and put a top hat on his own head -- I followed along. He did a little bit of "spirit fingers" and I followed. He did some hip gyrations; I followed. Of course, the audience was loving this and I love it when the audience is having a good time, so I'm having a good time.
Then he started the music: "You Can Leave Your Hat On," by Tom Jones, made famous in strip routines the world over and most especially in the excellent movie: The Fully Monty.
I can see where this is going.
So can the audience.
Now I'm trying to remember what underwear I'm wearing.
Our comedian starts into his routine, doing flips and tricks with his hat that I try to keep up with, but it's hard enough to balance a stiff top hat on your head, let alone a floppy cabby hat so, while I make the best of it, I'm only funny because I can't do what this guy is doing. The best bit, of course, is when he holds his hat over his crotch, I follow, he gyrates, I follow, he lets go of his hat, I follow, his hat stays up and mine... does not (obviously he's more likely to get laid at this point than I am : ).
After this, he pulls half his belt out and swings it around in a sexy manner; I follow, being as sexy as a giant, overweight geek can be (remember the fat guy from The Full Monty?). He throws his belt over his shoulder and I do the same.
And then the inevitable. The music builds to a fever pitch, he reaches down and pulls off his pull-away pants in one smooth motion, throwing them over his shoulder and the crowd goes wild.
Then he looks at me expectantly and the crowd goes even more wild (especially Joel who's nearly choking in laughter at this point). I raise my eyebrow to the comedian and he eggs me on. I raise my eyebrow to the audience and they egg me on. I remember that I'm no stranger to public nudity and a crazy audience is even more fun than a quiet photo studio, so I reach for my pants.
Of course, I'm not wearing my tear-away pants, so I'm laboriously unbuttoning and unzipping, following by carefully pulling off my pants over my sandals, which is not an easy thing to do without falling down when you're 6'5" and your center of gravity is someone near your left ear. But, I manage it and throw my pants over my shoulder, suddenly reminded of the underwear I chose for my day of revelry:
Now the comedian was nearly choking with laughter, but he said I did a great job and reached out to give me a hug (being careful to keep his hips as far away from mine as I was keeping mine from his), then shoo'd me off the stage. Then, while I'm still struggling to get my pants back on, the house lights go up, the act is over and the comedian is gone. And now, half the audience wants to shake my hand on the way out for showing off my polka dots on stage. It was a good way to start the weekend. : )
Saturday, July 16th, 2005
I wrote my first custom msbuild task this morning. I used the the Extend the MSBuild with a New Task topic from the msbuild wiki and it worked well to get me started. I started with the simplest thing that used at least an input property:
// HelloTask.cs
using System;
using Microsoft.Build.Utilities; // reference assembly of same name
using Microsoft.Build.Framework; // ditto
namespace MyFirstTask {
public class HelloTask : Task {
string _who;
[Required]
public string Who {
get { return _who; }
set { _who = value; }
}
public override bool Execute() {
Log.LogMessage(string.Format("hello, {0}!", _who));
return true;
}
}
}My task implements the msbuild ITask interface by deriving from the Task helper base class, which provides the Log object, among other things. The only thing I have to do is implement the Execute method, which needs to return true on success. To prove that my task is called, I use the Log object to log a message (I could also log an error or a warning). The public Who property is set from the use of the task in an msbuild file. By marking the property with the Required attribute, I ensure that msbuild itself makes sure that a Who is provided.
Once I've compiled my task, I can use it directly from a .proj (or .csproj or .vbproj) file:
<!-- fun.proj -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="HelloTarget">
<HelloTask Who="Joe" />
</Target>
<UsingTask
TaskName="MyFirstTask.HelloTask"
AssemblyFile="C:\MyFirstTask\bin\Release\MyFirstTask.dll" />
</Project>Notice the HelloTask element, which creates an instance of my HelloTask class and sets the Who property. The mapping between the HelloTask and the MyFirstTask.HelloTask class in the MyFirstTask.dll assembly is in the UsingTask element. Running msbuild against fun.proj yields the following output:
C:\taskfun>msbuild fun.proj Microsoft (R) Build Engine Version 2.0.50215.44 [Microsoft .NET Framework, Version 2.0.50215.44] Copyright (C) Microsoft Corporation 2005. All rights reserved. Build started 7/16/2005 7:04:09 PM. __________________________________________________ Project "C:\taskfun\fun.proj" (default targets): Target HelloTarget: hello, Joe! Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:00.04
Notice the "hello, Joe!" output by the task as its Execute method is called. Notice also that while the task is in its folder, the .proj file can be anywhere, so long as it has a UsingTask that maps appropriately. By convention, the UsingTask elements are kept in .targets files and put into shared folders to be used between multiple project files, e.g. Microsoft.common.targets, etc. Refactoring the UsingTask out of the .proj file and into a .targets file looks like this:
<!-- My.Fun.targets --> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <UsingTask TaskName="MyFirstTask.HelloTask" AssemblyFile="C:\MyFirstTask\bin\Release\MyFirstTask.dll" /> </Project><!-- fun.proj --> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="HelloTarget"> <HelloTask Who="Joe" /> </Target> <Import Project="c:\My.Fun.targets" /> </Project>
Of course, a real task does far more than this one, but it was hella easy to get started.
Sunday, July 10th, 2005
I'm sitting at my computer on Sunday morning with "nothing" to do (I mean, I could always work, but my team is good about taking weekends off). This morning comes after 3.5 months straight of evenings and weekends working on the Avalon book (I'm talking 20+ hours/week on the book on top of the 50-60 hours/week I spent getting up to speed on my new job). The final push was this week, which I took as vacation from work ("you took vacation to work!" my wife likes to say...).
Last night, I produced the 2nd draft of my last 1st draft chapter (which I was happy to trim by 17 pages w/o losing anything useful) and composed comments on a 2nd draft of Ian's chapter that was in my queue.
This morning, I took care of a reviewer comment that's been nagging me, sent Ian my feedback and composed a detailed schedule of the rest of my day which consists of:
Compared to how I have been spending my time lately, that's an extremely light day.
This book has been particularly difficult to write. Most of my writing has been on insights that I or the other members of the community have discovered in the use of the technology. These kinds of insights come after the technology is shipped and we've all had a chance to get to know it. Avalon, on the other hand, has a ways to go before it ships and the developer community is very small. Plus, some parts of Avalon don't work very well or have changed significantly since I first learned about them. The consequence of this is that most of my writings on Avalon have had to have at least one massive overhaul as I a) learn the best way to think about them and b) update them to actually reflect the latest bits.
The rub is that by the time the book sees the light of day (it should be on the PDC show floor), the Avalon team will likely have shipped another version of the bits, obsolescing what Ian and I have worked like dogs to ship. Of course, we'll post the errata and we'll update the book for the Avalon RTM, but still, it hurts that most of you won't be able to read the book when it's a perfect match for the bits.
I get to read it, though, and I'll tell you -- right now, the book rocks. : ) And the reason it rocks? Ian and I have worked hard to make sure it does, of course, but it's mostly been the internal and external reviewers that have done such a great job pointing out where we got it wrong. It's tough to hear, especially when it means a complete chapter re-write (I just finished one of those last night), but I'm so happy with the results that I'm willing to love them anyway.
Now I've raised the bar impossible high, but screw that -- I'm enjoying the moment...
Friday, May 27th, 2005
My son came to me the other day and said, "Dad, I need help with a math problem." The problem went like this:
We're going out to dinner taking 1-6 grandparents, 1-10 parents and/or 1-40 children
Grandparents cost $3 for dinner, parents $2 and children $0.50
There must be 20 total people at dinner and it must cost $20
How many grandparents, parents and children are going to dinner?
The reason this problem is interesting is because there are 3 variables, but only 2 equations:
grandparents * 3 + parents * 2 + children * .5 = 20
grandparents + parents + children = 20
Being a coder, I sat down to write the program to enumerate all possible solutions:
class Program {
static void Main(string[] args) {
for( int grandparents = 1; grandparents < 7; ++grandparents ) {
for( int parents = 1; parents < 11; ++parents ) {
for( int children = 2; children < 41; children += 2 ) {
double dollars = grandparents * 3 + parents * 2 + children * .50;
int people = grandparents + parents + children;
if( dollars == 20 && people == 20 ) {
Console.WriteLine("grand parents= {0}, parents= {1}, kids= {2}",
grandparents, parents, children);
}
}
}
}
}
}
Running this program saves my son the time to figure out the solution through tedious trial-and-error (plus, he didn't even have to write the program, since I did that part -- tricky little bastard, isn't he?!?) by producing the following output:
grand parents= 1, parents= 5, kids= 14
That was all well and good 'til Alex, a friend of mine, boiled the problem down to a single-statement Prolog program for me (although this program doesn't capture the range of values the variables can take on):
people_at_the_meal(Grandparents, Parents, Children) :- Grandparents * 3 + Parents * 2 + Children * 0.5 = 20, Grandparents + Parents + Children = 20.
Then I went on to regale Alex about the time my grandmother asked me to schedule her tennis tournament for her on my computer. She had requirements like, "everyone has to place everyone else at least once" and "you have to lose twice to be out of the tournament" and "nobody can play twice in a row" (it's not good politics for a computer program to kill old ladies...). My grandmother's problem statement doesn't fit the traditional algorithmic statements that I'm used to using computers for, nor was I ever able to re-form the problem in those terms (it's not like my grandma was ever going to leave me a bunch of money anyway...). Alex completely understood and informed me that the NBA has the same problem (with slightly sprier players) and they use logic programs to solve it. In fact, in his experience, these problems happen all the time in the business world.
Constraint Logic Programs (CLPs) break down into constants, variables over ranges and relations of truth, which together make up the constraints in a logic system. In my son's case, the constants are the numbers, e.g. 3, 20, etc, the variables are the number of people of each type and the relations form the constraints, e.g. the sum of all people must be 20. The CLP "solver" (in Alex's parlance) provides the logic over a particular "domain" (real numbers in our case) and knows how to do all the iteration and forward and backward chaining to solve the people_at_the_meal problem. A different solver would be able to solve my grandmother's and the NBA's scheduling problem.
It was only after most of this discussion that I realized that I recognized the syntax that Alex had produced: it was Prolog, a CLP language I had blocked. I had taken a Prolog course in it in college and absolutely hated it, but not because of the things that allowed me to solve these kinds of problems: that was great. The things I hated were all of the algorithmic things that I needed to be able to do that Prolog was terrible at, e.g. take input, product output, suck in facts from data external to the system (like the NBA player roster), etc. CLPs themselves are damn cool.
Monday, April 11th, 2005
Last week was my first week on my new team. A lot of interesting things happened, some of which Don has already mentioned. For me, two especially interesting things happened. One, I found out why I was hired, in spite of my preference to work from kitchen most of the time, even though I'm working on a product group (which typically avoids my kind unless they've already worked for the team for a long time) and even though I'm working for a manager that's never had a remote employee (he doesn't even run IM!). Don't get me wrong; it's not like a skated through the interview process w/o "passing." But "passing" isn't enough when you work in a "shower optional" environment (my house, not Microsoft : ). My team needs people with a skill that I've never seen applied to product development. Most of the people that have this skill are either a) already busy shipping Indigo or b) marked as a target (some of you should feel the laser sights on your forehead : ). The skill is the thing that allows me to write books and articles in my own special way. My team has a name for this skill; it's called "wallowing."
For my team, the ability to "wallow" means the ability to dig into a discipline, any discipline, and "wallow like a pig in the mud" in that new discipline, forming questions, getting to the root of things, gathering fragmented truths together into wholes, communicating the truths to each other, gaining intuition, and generally spending time understanding the space without first coming to firm conclusions about what it is that we're actually going to build. This skill is by far the most important thing I learned at DevelopMentor. The "popping" sound I got in my head when I finally figured out COM is something that I'll never forget (mostly because my head stopped hurting after 6 weeks of hurting very much) and it's served me well over the years. For example, I wrote my WinForms book not because I had a ton of "real-world experience" as my Amazon reviews claim, but because I knew very little about WinForms and was interested in learning. I wallowed in WinForms for 9 months, writing articles and an instructor-led short-course before ever writing a single word in my book. Once I'd wallowed, however, I was able to write the book itself in 5 months.
So, while I've used to wallowing as a way to produce talks and prose, it's not a technique I've ever applied to product selection. In the past, I've come to a team where the goal was very clearly known, although the details were still up in the air (often 'til they were discovered as coding problems). I've even started my own software organization from scratch, but only after the core vision of what needed building came to me whole and I'd already built the prototype. However, with my new team, we've got a group of folks actively wallowing in a space, figuring out the right thing to build. This led to some initial frustration on my part.
Actually, frustration is too weak; for the 3 days of my employment, I was a drooling idiot with no hope of recovery. I'm used to not knowing stuff (that's when the wallowing behavior is triggered), but I'm not used to not knowing what I don't know. The reason that I was so frustrated was that I didn't know my team was wallowing. My team has already been working together in this problem space for months. They've already put together a prototype and showed it to BillG. This evidence made think that the team already had a firm vision of what they were building, so I was in coming-to-a-team-to-help-build-a-known-product-mode as with every single product team I'd ever been on or heard of.
Firmly in this mode, I spent my first 3 days in architecture meetings, brainstorming our way through the main issues of our "product." At each meeting, it seemed like people had a firm idea of what they were building, because the items on the brainstorm list flowed like water from everyone but me. To give myself some ground on which to stand, I kept asking whether we were going to cover this customer scenario or that one and the answer would always be "yes."
This went on for three days and each day I felt like I knew less about what we were doing. Instead of drawing up a plan to build a v1 settlement on the moon, I felt like we were listing the issues associated with covering the entire surface with a 10-story city. My head was swimming. How could a brand new team be targeting that kind of scope? What the hell had I gotten myself into?!?
I'm not the kind of person that keeps frustration to myself. I spent a good deal of time trying to express my despair to my boss and my new team mates, but it took me 3 days to be able to wind back my assumptions enough to be able to ask, "Do we even know what we're building?" The answer that shined light into my gloom was "no, we're wallowing."
And suddenly, all the conversations made sense. When they kept saying "yes," they meant "yes, we're still considering that possibility." We were discussing enough issues to cover the surface of the moon not because we were going to do that in v1, but because we were still figuring out the right place and kind of settlement to build. We'd picked the moon and we'd put up a tiny shed to see what it was like to live there, but we hadn't yet figured out what the next wave of settlers would need. And because we didn't want to die in the process or kill that next wave, we were going to spend some real time exploring to make sure we were proud of our first city.
This process was a revelation to me. In the small to medium-sized software engineering projects in which I'd been involved to date, this kind of product planning was outside of my experience. I mean, even when I plan to wallow in the implementation details, I've never once wallowed in the issue of what to build. When I picked my WinForms book, I didn't survey the .NET developer landscape for their needs; I just picked something I was interested in. I got lucky, but in the case of other projects, I have been less so (and sometimes expensively, spectacularly less lucky).
I told my sister-in-law this story and she was all ready to be upset for me. "You mean, they never told you? You signed up to do product development and they don't even know what they're building?" she asked. I told her that I wasn't upset at all. In retrospect, I was told that my team was wallowing, it's just that my preconceptions about what a product team does at Microsoft blocked it out.
Further, this way of figuring out what to build is one I'm looking forward to. I've never been any good at this part of the product construction process. In fact, I once drove 2 hours north and cajoled Eric Sink into driving 2 hours south so that I could ask him how to answer this very question (micro-ISVs sound cool, don't they?). His advice was "fail fast." That's good advice and, having talked to my new boss, it's something we'll be doing, but we hope to do it while exercising wallowing as a product selection technique.
The second thing I learned this week is that, while I didn't know this 'til after my 3rd day, my team is still wallowing. I told my sister-in-law that I wasn't upset at all. I love wallowing. : )
Friday, April 1st, 2005
Today's my last day at MSDN. I will no longer be content strategizing for Longhorn/WinFX or smart clients or acting as liaison between marketing, evangelism and the product teams internally and developers externally. And I'm really going to miss it. MSDN took a flier on me being a successful Microsoft employee in a culture that doesn't much like remote folk and made a very comfortable home for me. I could've gone on for a long time in that role.
Still, I felt another calling. At Microsoft, it seems like all of the action is with the product teams. To come to Microsoft as a software engineer and not work on a product team seemed like starting with the Yankees but never getting out of the dugout.
So, on Monday, I start in DSG (the Distributed Systems Group) aka the team that owns Indigo. I'll be "are looking at ways to extend the notions of modeling that we've incorporated deeply into the stack and make them more general" (or so my new boss says). I can't tell begin to describe how lucky I feel. Not only do I get to be on a real product team, still mostly from my house in Oregon, but I get to work on a very juicy problem. Further, I get to do it with Oliver Sharp, one of the prime movers on the Indigo team, along with a growing group of other people way smarter than I. It reminds me of the heyday of a certain training company with which I once had a deep relationship.
Two weeks ago, Oliver sent me a list of 10 fun computer sciency things to dig into that could take months. Last week I was helping the team get ready for a BillG review. This week I was trading emails with the team and a Sr. VP on the direction of the market and how it affects our product plans. Is this what it's like for an addict that's given up their habit to take it up again? If software engineering is wrong, I don't want to be right! : )
Thursday, March 30th, 2005
I'm writing a tiny little application for my writing on ClickOnce in Avalon. In fact, to demonstrate ClickOnce, the app hardly matters, so I picked something really simple: an excuse generator. I didn't even have to make up the excuses, as I stole them from an office gag gift two years ago and wrapped them in an excuse web service (which is a whole other story : ).
The idea was to have an array of excuse strings like so:
public partial class Window1 : Window {
static string[] excuses = {
"Jury Duty",
...
"It's Not My Job",
};
public Window1() {
InitializeComponent();
this.DataContext = Window1.excuses;
}
...
}Once I had the collection of strings, I make them available for binding by setting them to the DataContext of the main window and the rest of the code would simply be a matter of binding a TextBlock to the current excuse like so:
<Window ...>
...
<TextBlock TextContent="{Bind Path=/}" />
<Button x:ID="newExcuseButton">New Excuse</Button>
...
</Window>By binding the TextContent property of the TextBlock to a Path of "/", I'm explicitly saying "don't try to dig into each object in the collection looking for sub-properties, but binding to the item itself." Initially, the TextBlock will show the first item in the list:

When the button is pressed, I change the output by selecting a random item from the collection and setting it as the "current" item in the collection:
Random rnd = new Random();
void newExcuseButton_Click(object sender, RoutedEventArgs e) {
ListCollectionView view =
(ListCollectionView)Binding.GetDefaultView((IEnumerable)Window1.excuses);
view.MoveCurrentToPosition(rnd.Next(view.Count - 1));
}By grabbing the view for the excuses collection, I can do a number of things with it, including move the current item in the view to some specific position. When I do that, the data bound TextContent property of the TextBlock will be updated to show whatever is current:

So here's the first thing that I'm notice that I really approach differently in Avalon: the data binding hammer is almost always the first tool I reach for in the programming box. In the old days, to write this app, I'd set the TextContent property directly in code. Now, I don't even think to do that. Instead, I've got data and a property to set, so that's data binding.
"But wait!" I hear you howl. "What about internationalization?!?" I know. Hard-coded data in the source code is a bad idea. So where does it go? Into the XAML itself, of course, so that anyone working on the UI, whether for an English-speaking country or not, can add new items, port them to another language, etc. How to do it? Well, since XAML is an XML dialect for describing object hierarchies, I could define a new type (roughly):
class Excuse {
string value;
public string Value { get { ... } set { ... } }
}
class ExcuseData : List<Excuse> {
}Then I could write my excuse data as a hydration of objects using the ObjectDataSource (roughly):
...
<Window.Resources>
<ObjectDataSource x:Key="ExcuseData">
<l:ExcuseData>
<l:Excuse Value="Jury Duty" />
...
<l:Excuse Value="It's Not My Job" />
</l:ExcuseData>
</ObjectDataSource>
</Window.Resources>
...The XAML inside the ObjectDataSource will create an instance of the ExcuseData type, adding an object of type Excuse for each <Excuse> element, setting the Value property of the object using the Value attribute from the XAML. With the object data source, I can create a little "data island" in the middle of my app where the list of excuses can be poked and prodded without touching the code.
Still, why go to the trouble of defining my own custom data type, which doesn't have any behavior to speak of, when I've got the universal behavior-less data type -- XML? I can use the XmlDataSource without any custom type at all:
<Window.Resources>
<XmlDataSource x:Key="ExcuseData" XPath="/Excuses/Excuse">
<Excuses xmlns="">
<Excuse>Jury Duty</Excuse>
...
<Excuse>It's Not My Job</Excuse>
</Excuses>
</XmlDataSource>
</Window.Resources>
You'll notice that the XML looks almost exactly like the object data source, except that instead of using an attribute to keep the juicy bits, I use the content area itself to store the data, very like the initial array example (I could have used XML attributes, but it seemed overkill in this case). One other interesting bit is that XPath of the XML data source itself. It defines the bit of the data island from which I'm pulling the data. If I had used /Excuses, the data collection would have had a single item with a bunch of children. But using /Excuses/Excuse, I'm exposing the children directly.
To use the XML data source, I need to set it as somebody's data context in the hierarchy of controls and bind to it:
<Window ...>
<Window.Resources>
<XmlDataSource x:Key="ExcuseData" XPath="/Excuses/Excuse">...</XmlDataSource>
</Window.Resources>
<Grid ... DataContext="{Bind DataSource={StaticResource ExcuseData}}">
...
<TextBlock ... TextContent="{Bind XPath=.}" />
<Button ... x:ID="newExcuseButton">New Excuse</Button>
</Grid>
</Window>I should note that in future bits, I'll be able to set the grid's data context to {StaticResource ExcuseData} directly, but in the March 2005 CTP Avalon bits, I have to actually bind the data source to the data context in a double bind that blows my mind.
More importantly, notice that instead of using a Path to get to the data I want, I use an XPath. This allows me to further refine the XPath statement provided as part of the XML data source itself. However, since I don't want to refine it any further, I use the XPath statement that says "just use the content of each of the elements." You'll notice that I used "." in the XPath statement to provide this meaning instead of "/" as I did in the case where I was using an object Path. The two syntaxi look the same, but are very different and you'll want to watch yourself switching between the two.
Anyway, with the data moved to the XAML along with the UI and the binding logic, my actual code boils down to only the following:
public partial class Window1 : Window {
Random rnd = new Random();
public Window1() {
InitializeComponent();
this.newExcuseButton.Click += newExcuseButton_Click;
}
void newExcuseButton_Click(object sender, RoutedEventArgs e) {
IDataSource dataSource = (IDataSource)this.FindResource("ExcuseData");
ListCollectionView view =
(ListCollectionView)Binding.GetDefaultView((IEnumerable)dataSource.Data);
view.MoveCurrentToPosition(rnd.Next(view.Count - 1));
}
}So, this is really only a few lines of code, one to hook up the button event handler, one to find the excuse data from the window resources, one to get the view on the excuse data and one to move the currency pointer to some other random spot. The actual display of the data is left to the data binding code (which somebody else gets to maintain).
One other thing worth noting is that I had defining event handlers in the XAML. Even when it's me writing both the XAML and the code, I don't like the XAML making demands on the code. If the XAML handles an event, e.g. with an event trigger, than that's fine, but don't make requirements of the code form the XAML. Instead, provide hooks for the code to do its work, e.g. the x:ID on the ExcuseData and the TextBlock are both used in the code, but neither requires anything of the code. If you're going to separate the presentation from the logic, this is the kind of thing you'll want to think about.
Two things I'm finding that Avalon has changed about my thinking. The first is that data binding makes itself into even trivial Avalon applications and its presence is appreciated. The second is that I want to push as much stuff into XAML as I can. Keeping the data separate from the code makes a bunch of sense and, for my trivial application, keeping the data inline with the rest of the UI was very useful. It allows for easy maintenance and localization while pushing as much of my application into declarations and out of imperative statements. The more I can tell the computer what I want instead of how I want to accomplish it, the better off I am.
Wednesday, March 30th, 2005
For an internal thingie, I was asked to write a short bio, define what I think a "leader" is, describe my target legacy and list my biggest source of price. This is what I came up with:
I've been pretty much everything you can be in the IT industry including author, speaker, consultant, conference organizer, grunt in a start-up, CEO in a start-up, developer, tester, documenter, etc. I'm currently a content strategist for MSDN in the areas of WinFX/Longhorn and smart clients. I'm passionate about building products to make people happier, whether that's because they're more productive or because I've solved some hard problem or just because they lean back and say "cool!" My biggest regret is that I've never yet been on a product team at Microsoft; to get the full experience, someday I need to do that! After that, I can retire and write novels from coffee shops around the world. : )
A great leader is someone that inspires you to do things you didn't even know you were capable of doing.
The legacy I'd like to leave behind is that people can look back at their interactions with me and think that I helped make their lives better, whether it was an answer to a tough question or an insight that they hadn't had before or a comment that tickled their funny bone.
The biggest source of pride in my life is the family I've built with my wife and two sons (the Sells brothers, whence the name of my web site came: www.sellsbrothers.com).
Friday, March 11th, 2005
I was just putting together a list of some of my favorite books as recommendations for a gift certificate I sent to a friend as a birthday present. If case you're wondering (I know you're all dying to know : ), here's what I sent:
Wednesday, February 23rd, 2005
I do not consider myself weak-minded. For example, even though I've tried to let it happen, I've never been able to be hypnotized. Also, I'm often considered to be close-minded (although I do change my mind when valid arguments are presented, but most folks don't argue very well, I find). So, why did a terrible "High Risk Driver" course change my driving habits?
The course started as you'd expect: a room full of 18-25 year olds that did not want to show up anywhere on a Saturday morning, least of all at the local level 1 trauma center for 8+ hours of lecture. The main instructor was a high-energy trauma nurse that professed an vast personal experience with all things alcoholic. The traffic cop drove a motorcycle, which he made great pains to point out was the most dangerous vehicle to drive, and told a story about how he let a friend of his drive while they were both too tired in spite of his personal expertise in all matter of influence while driving, e.g. alcohol, drugs, cell phones, sleep deprivation, etc.
Of course, there was the obligatory "Faces of Death" presentation through-out the day, but they were slides, not videos, so didn't compare to the movies I saw in driver's education class at age 16. Even the stories of the people in the pictures, while sad, seemed as much about capricious bad luck as about actual bad decision making. Some of the people from the stories even came to speak to us in their wheel chairs (except one guy that came in a suit and passed his business card around), but by far the most convincing and articulate of this bunch got his injury from a diving accident (that's diving not driving).
The crowning event for the day was the tour of the trauma center itself. It was filled with people who'd suffered traumas, but the vast majority of them were there for non-traffic related injuries (unless you count the guy that rode a sled into a parked car on the one day we had any snow this year). How looking at the new, state-of-the-art MRI machine or seeing nurses drink coffee on a raised platform in the middle of the room helped us learn to drive more carefully, I have no idea.
And yet, despite my best efforts to avoid engaging with the materials of the class, I find that I am driving more carefully. I was always good at keeping my eyes on traffic and watching for kids and animals on the road, being quick to slow or swerve when necessary and rarely getting mad at the other drivers for bone-headed moves (unlike my wife, who curses every 3rd driver : ). However, now I find myself nearer the speed limit more often, sometimes under but always within 5 or 10 miles. And now, while I do still change lanes, it's most often when I'm behind something large that gets in line of sight and not to get ahead a car length of two.
So, what changed my habits? It wasn't the lame course as a whole, but it might have been one or two moments. It might've been when the traffic cop, when asked for the most dangerous driving habits, listed speeding and aggressive driving, which I'd previously considered the least dangerous.
It might've been the statistics. It's not like they showed many convincing statistics at all, but the mere fact that they had been giving this lame course 4-6 times/year for the last 17 years spoke to me about the need for some kind of intervention. Obviously, the sponsors of this course thought it was having some kind of effect, else why continue it?
Or it might've been the company. I mean, their were some real losers in the room, including a 16-year old that admitted to a long list of bad decisions right out of your favorite gang movie. Frankly, to be lumped in with this crowd was just plain embarrassing.
However, when all was said and done, I think it was the math. The traffic cop timed himself doing his 10-mile commute going the speed limit and going 15 miles over the speed limit. The difference w/ zero other traffic on the road? 2 minutes. Do I really need to engage in what are considered the most dangerous driving habits, risk my insurance, my car, my license, my life and the lives of the people around me for 2 lousy minutes?!? That's a bet I can only lose.
Saturday, February 12th, 2005
When I used to teach COM, we would brag about its cross-platform-ness, i.e. it worked across Windows 95, Windows 98, Windows NT 4, etc. So, when I'm talking about Windows alternatives, of course I mean Windows XP, Windows XP Media Center Edition, Windows CE and Windows XP Tablet Edition:
Windows XP: This is the workhorse of my day and with SP2, it's even more wonderful than it was before. Love it.
Windows XP Media Center Edition: The mix of a pleasing 10-foot UI, picture, music and video playback, content and UI extension, PVR, Windows-based expansion and MCE extenders makes this a fabulous media client and server. Love it.
Windows CE/SmartPhone 2003 Second Edition: The 10-inch UI, the built-in apps and services, the extension apps, the internet access and a set of apps optimized for 9-key + joystick input all make this a fabulous user platform that's replaced my phone, my mp3 player and even my laptop in some cases. Plus, I can build my own apps! I haven't felt this way since my first laptop freed me from the tyranny of the desktop. Love, love, love it!
Windows XP Tablet Edition: I love reading text on this OS. The ClearText, the form factor and the scroll-specific buttons near the screen make it a wonderful reading experience. I also look forward to reviewing articles and book chapters with my tablet. However, I doubt I'll get to it because I dread picking it up and being faced with all of the text input I'm forced to perform. Unlike the MCE or SP OSes which have been optimized for remote control/keypad input, the tablet is more general purpose and therefore supports far more general purpose applications. This means text input for dialogs and passwords and URLs and all kinds of other things where my poor handwriting is often painful. Of course, handwriting is faster than joystick/keypad input, but because there seems so much more input required in an average tablet session, it seems slower. I with the apps for the tablet were more special-purpose and optimized for stylus-only input. Love it and don't so much love it.
BTW, I paid $6000 in college for my Mac IIcx that I used to log into the Unix machines in the lab, so I've had a full range of computer UI experiences. I do truly love Windows best.
Saturday, January 1st, 2005
When something legal comes up, I like to dig into it. For example, I did my own consulting contracts for years, I've testified against an Oregon state bill mandating Open Source Software (a bill that was never passed), I wanted to serve on a jury and I've recently become very familiar with the Oregon state eviction and small claims procedures for a rental unit I own. I find laws and procedures to be fascinating and, like most things, showing up is 80%.
However, I don't like the law so much when I'm the defendant, as was recently the case when the state of Oregon was hell bent on suspending my driver's license. It seems that if you get 4 tickets in 2 years that they like you to stay home for 30 days or at least take the bus more. I've had speeding tickets on and off for most of my life, but before this 2-year period which included a total of 6 tickets (thank goodness that the state of Oregon doesn't count tickets in Washington), I hadn't had a ticket for about 5 years. Incidentally, if I could manage to spread them out in a Bell Curve instead of in a Mandelbrot Set, I wouldn't even have to be in this predicament, but natural has it's laws and who am I to consider myself above them?
Anyway, the tickets should hardly count. One was 'cuz I didn't slow down fast enough in one of those coastal towns where half of the tourist revenue comes from their 100 feet of road along the highway that drops from 55 to 25 with a sign posted behind a tree. Another was on the way to Burning Man. Sure, I was going a little fast, but I spent the next week driving the dusty BM roads on my bike -- can't we do some averaging here?!? Another was an actual speeding ticket around town and that one I'll admit to, but the forth one was when I passed into a turning lane through a solid yellow line in rush hour traffic 'cuz I wanted to turn left and the line of traffic was way shorted than the line of traffic going straight, so I cheated over a few feet early. I mean, come on, that's just an efficient distribution of traffic! I was avoiding gridlock! It was practically my patriotic duty to move over into the left lane early!
However, the truck that was waiting patiently to move over into the left turn lane until he had passed the solid yellow line just ahead of me didn't see it that way. Nor did the police car that had just turned the corner coming towards me as I scooted around the truck, neatly avoiding the oncoming traffic, all executed flawlessly while my wife dug her nails into my leg.
So, I did what I do with all my tickets: I signed the back and I paid the money. I mean, it's not like my hairy cleavage is going to change any cop's mind, so what else could I do?
When the letter from the state came that said they'd like me to turn in my driver's license for a month is when I started exploring options real quick.
So, I called my tax/business/family attorney, who gave me the names of three traffic attorneys. Unfortunately, each of them specialized in Driving while Under-the-Influence and I began to think that I'd have been better off if I'd have been drunk. Each of them advised me that there was nothing anyone could do for me. I signed the ticket and I was going to have to give up my license. I also felt a very "and why are you wasting my time, you sober slug, I've got important drunk people to talk to" vibe, but that could've just been me...
Luckily, once it was clear I was going to hang up the phone without giving the 3rd lawyer money, he mentioned another attorney that specialized in sober traffic offenses. This new attorney was one smooth operator. After about 5 minutes of questions over the phone, he'd figured out the "conviction" (when you sign the back of the ticket, you've agreed to be "convicted" of a crime) most likely to be turned back to trial (the passing violation, of all things). How did he determine the likelihood of getting a trial? He knew the prosecutor responsible for that part of the state! He was going to call the guy up, ask him for a solid, they run it by the judge as a formality and bang! I've got a new trial and my suspension was suspended pending the outcome of the trial.
And what did I have to do? Show up in court and plea my case? ("You want the truth! You can't handle the truth! You want me crossing that line! You need me crossing that line!") Nope.
Bring three character witnesses? (I have pictured Rory on the stand trying to provide a credible character assessment. : ) Nix.
Lie under oath? ("Yes? Mr. Sells? This is Nancy Reagan. I'd like that Eagle Scout Award that my husband signed back, please. Seems you lied to get out of a traffic ticket...") Nine.
All I had to do was send in a $750 check and do it quickly, please. As soon as it cleared the bank, I'd have my new trial (although it's illegal for him to give me "odds" on my ability to get a new trial, my attorney was clear that it wouldn't be a problem).
Plus, I didn't even have to go to the trial. In fact, in a very carefully worded letter from my attorney, he let me know that I should only come to court if I could say that I didn't do it with feeling and confidence. If I couldn't do that, I should just go about my business. Note that he never actually asked me if I did it or not. I find that a particularly fascinating and scary part of our legal system.
Anyway, at the trial, my "conviction" (I have to put it in quotes or you might think I was a criminal [I mean, just 'cuz I committed a crime, doesn't make me a criminal, does it?]) was over-turned in exchange for 8 hours of "driving school." This time, I'm using the quotes because there ain't no actual driving schooling going on. Instead, this is a replay of those driver's education movies where they show you "Faces of Death 3" in an effort to scare you onto the straight an narrow in a very Old Testament, fire and brimstone sorta way. Here's the descriptive paragraph from the letter I got in the mail yesterday:
"You have been given the opportunity to participate in the High Risk Driving Course... The course is designed to educate drivers to the potential and very real consequences of 'high-risk' driving behaviors. You will spend eight hours [in the course]. During this time you will hear several presentations on the consequences of high-risk driving, have contact with victims of traumatic injuries and their families, and discuss death as possible consequences of high risk driving choices. Participation in the course will include group discussions and testing, including essay questions and an evaluation of the course. We anticipate that facing the reality and potential consequences of high-risk driving behavior will positively affect the attitudes and behaviors that contribute to choices which place [you] and others at risk of serious injury and death... I understand that what I see and hear in this program is meant to have an emotional impact on me. I may experience psychological discomfort..."
I believe that participation in this program should qualify me as a "bad boy" and thereby make me the recipient of all of the benefits thereof, e.g. the alleged "good girls" that can't stay away from "bad boys" should now feel free to throw their undergarments onto the stage and to mob my limonene (it's a white '98 Volkswagen Cabrio Convertible "limo" [note the quotes] in case you're confused). The fact that my financial portfolio is fully diversified using CFA-certified asset allocation techniques, that I was a Boy Scout, that I'm married to my first real girlfriend, that I was a member of the marching band, that I was going 20 miles/hour when I committed my most recent traffic "crime" and that I wouldn't actually drive with a suspended license, thereby causing all kinds of havoc in my life, should not detract from my obvious reckless, dangerous, "high-risk" persona. Anyone need a bad boy for their boy band? I'm available...
2001-2004