Ayende @ Rahien

It's a girl

Thinking about partial classes

I'm thinking about the way ASP.Net are working, and I can't help thinking that I'm missing something with regard to partial classes. The issue here is that of how the compiler knows to take the aspx page and turn it into code.

Is it done it the compiler level in the first place? If so, how does the compiler knows about it? Is it possible to get your own code to run when this is done?

I think that the answer to those is no, and that it is just the magic of ASP.Net & VS.Net that make it looks like it's happening. And ideas about it? I'm interested in generating add-on content for a class during compilation, and I think that partial classes and attribute based code generation might put me on an more even footing on C# as I am on Boo.

Right now it looks like I need to:

  • Write a Custom Tool to do it, and register as a pre compilation step in VS.Net (MsBuild?)
  • Parse the source files, in order to figure out which class has the correct attributes. Any suggestions on how to do that? I need to do it before compilation.
  • Spit out gobs of code.

The part about parsing worries me, it not going to be pleasant to do, and I can't think of an easy way to get the AST for a C# file.

Tags:

Published at

My First Trigger

So, today I wrote my first trigger. The issue was of data replication on a hierarchial structure. When the parent is updated, all the children should be as well. If a child is update, the link to the parent is severed. The number of children is unlimited, so I can't use NHibernate to do the update, as it would be horribly inefficent to do batch update with it.

The parent and child in this case is one of ~15 classes (one inheritance family) that has different meaning for the same fields in the database. They are mapped using Table Per Inheritance in NHiberante (using ActiveRecord). At first I despaired from doing this properly, I thought about having to do it 15 times, with minor modifications... Even seperating the common functionality, I would still have a lot to write. Then I remembered that I'm using ActiveRecord, and that it can gives me the information about a class, so I quickly wrote this code:

/// <summary>
/// Copies all the values of the current parent rule to all its children
/// iterate over the model for the rule and gets all the properties and fields.
/// </summary>
protected virtual void UpdateChildren(IDbConnection connection)
{
 using (IDbCommand command = connection.CreateCommand())
 {
  command.CommandText = Resources.UpdateAllChildRules;
  CrearteParameters(command);
  command.ExecuteNonQuery();
 }
}
protected virtual void CrearteParameters(IDbCommand command)
{
 ActiveRecordModel model = ActiveRecordBase.GetModel(this.GetType());
 foreach (PropertyModel propModel in model.Properties)
 {
  CreateParameter(command, propModel.PropertyAtt.Column,
   propModel.Property.GetValue(thisnull),
   propModel.Property.PropertyType);
 }
 foreach (FieldModel fieldModel in model.Fields)
 {
  CreateParameter(command, fieldModel.FieldAtt.Column,
   fieldModel.Field.GetValue(this),
   fieldModel.Field.FieldType);
 }
}
protected void CreateParameter(IDbCommand command, string name, 
 object value, Type type)
{
 IDataParameter param = command.CreateParameter();
 if (type.IsDefined(typeof(ActiveRecordAttribute),false))
 {
  ActiveRecordModel model = ActiveRecordBase.GetModel(type);
  PrimaryKeyModel pkModel = (PrimaryKeyModel) model.Ids[0];
  type = pkModel.Property.PropertyType;
  value = pkModel.Property.GetValue(value, null);
 }
 param.ParameterName = "@" + name;
 param.Value = value;
 param.DbType = GetDbType(type);
}

As I wrote this code, I kept muttering things about Evil Code and how the one to follow after wouldn't be able to understand anything. This is using some deeply internal knowledge of Active Record, and it's not very nice for someone who doesn't know how Active Record works. I just couldn't see a better way. And considerring my options, I thought that this was the least Evil among them.

Then a friend offered me to use a simple thing. Triggers. The database I'm using for the project in SQL Server 2000, so that is not a problem, but while I leapt at the idea (much clearner than the above code), but there was an implementation hurdle to go through, I never wrote a trigger before, certainly not for SQL Server. The only one around I could ask used to write triggers for Oracle, which wasn't much help.

So I scoured the books online, and discovered what I already knew, that SQL is a very different language than my safe OO world. I think that I got it write, and small tests shows that it's working properly, but I'm still not sure about it.

CREATE TRIGGER TR_Update_RuleChildren_On_Rule_Update     
ON [Rules]
FOR UPDATE AS
SET NOCOUNT ON
DECLARE @UpdatedRuleId int, @UpdatedSeverity tinyint,
@UpdatedTemplateRuleId int
SELECT @UpdatedRuleId = [RuleId], @UpdatedSeverity = [Severity],
@UpdatedTemplateRuleId = [TemplateRuleId] FROM [Inserted]
IF (@UpdatedTemplateRuleId IS NULL) -- is a parent rule
BEGIN
UPDATE [Rules] SET [Severity] = @UpdatedSeverity
WHERE [TemplateRuleId] = @UpdatedRuleId
END
ELSE
UPDATE [RULES] SET [TemplateRuleId] = NULL
WHERE [Rules].[RuleId] = @UpdatedRuleId
SET NOCOUNT OFF
go

As I said, this is the very first I've written, so I'm absolutely not certain if I did it right. Is this the way to do? It make me cringe to see it, since it violate so many things I know are right. But I guess that they don't apply to a database.

Boo's Design By Contract Update

I didn't have a lot of time recently to finish the Design By Contract for Boo. I did manage to push it forward somewhat in that it actually works :-)

It doesn't yet works with inheritance, so that is yet a todo.

You can get the new code here

Tags:

Published at

PDC Videos

I currently watching Raymond Chen talk. It's so cool to be able to see what all the excitement was about. It a shame that the price for the DVD set is so high (500$ ?!).

I'm really glad that Microsoft decided to make them avialable, even if not to the FireFox using crowd. I'm scanning the archives now, looking for good talks. There is so much stuff there.

Rico Mariani's presentations, GC Inside out, Concurrency, etc.

Best quote so far, from Raymond's talk: "I can do nothing really fast."

#Develop Update

The #Develop IDE just got a whole lot better, they fixed the generic type inferencing.

? dic = new Dictionary<string, List<KeyValuePair<string,int >>>();

Turns to:

Dictionary <string, List<KeyValuePair<stringint >>> dic = 
     new Dictionary<string, List<KeyValuePair<string,int >>>(); 

Published at

Generic using alias

Why can't I do this:

using Hashtable<TKey, TValue> = System.Collections.Generics.Dictionary<TKey, TValue>;

I can do it if I specify the generic parameters, but not while keeping the generic parameters free.

Tags:

Published at

Static Reflection

I've a confession to make. It's a very serious one. I don't like strings and Reflection. In fact, you could say that I hate them with a passion. I hate them because they hide important information from the compiler. This post is actually not a rant, it presents a solution to the problem.

I hate them so much that I wrote my own Mocking Framework that don't use strings at all. .Net has some really great stuff that you can do with Reflection, but it has two big problems.

  • It is a performance hit.
  • It uses strings.

I refactor a lot, and I just hate it when everything compiles okay, and then things breaks on runtime. I think that static reflection will enable writing amazing code. The functionality is already built into the CLR (check the ldtoken/ldftn IL instructions and friends). It didn't make it into the 2.0 release, but I hope that it will be in the next release. In the meantime, the 2.0 Reflection has been optimized, so that is what we have for now. But it's not enough for me. I discovered that you can do this:

Action<int> act = this.CalcSums;

So I figured out that I can create a library that would cheat the C# compiler (and I belive the VB.Net compiler as well) to give me a token for the method. Before I'll get into the details, here is a short sample of the code:

using System;
using System.Reflection;
public class StaticReflection
{
        #region void delegates
        public delegate void VoidFunc();
        public delegate void VoidFunc<A0>(A0 a0);
        #endregion
        #region delegates
        public delegate TRet Func<TRet>();
        public delegate TRet Func<TRet, A0>(A0 a0);
        #endregion
        #region void func Method Info
        public static MethodInfo VoidMethodInfo(VoidFunc func0)
        {
                return func0.Method;
        }
        public static MethodInfo VoidMethodInfo<A0>(VoidFunc<A0> func1)
        {
                return func1.Method;
        }
        #endregion
        #region func Method Info
        public static MethodInfo MethodInfo<TRet>(Func<TRet> func0)
        {
                return func0.Method;
        }
        public static MethodInfo MethodInfo<TRet, A0>(Func<TRet, A0> func1)
        {
                return func1.Method;
        }

        #endregion
}

The usage is simple:

public class Test
{
 public static void Main(string[]args)
 {
  MethodInfo thisMethod = StaticReflection.MethodInfo<int,int>(MultiplyByThree);
  Console.WriteLine(thisMethod);
 }
 
 public static int MultiplyByThree(int i)
 {
  return i * 3;
 }
}

The output of the above code is: "Int32 MultiplyByThree(Int32)"

Now, there is one huge advantage of this system, and it's the simple fact that it will fail to compile if you changed the name of the method and not the calling code. The other advantage it should have is in performance.

In order to test that I created two sets of tests. The tests are two classes, each with 10,000 methods, each class Main() needs to get a MethodInfo object for each of the methods. Here is a sample of the code:

Benchmarking the static reflection:

public static void Main(string [] args)
{
 DateTime start = DateTime.Now;
 MethodInfo method;
 method = StaticReflection.VoidMethodInfo(DemoClass0.DemoMethod0);
// ... 9998 more statements ... 
 method = StaticReflection.VoidMethodInfo(DemoClass9999.DemoMethod9999);
 Console.WriteLine("Took: {0}", DateTime.Now - start);
}

Benchmarking normal Reflection:

public class TestingUsingReflection
{
 public static MethodInfo GetMethod(Type type, string method)
 {
  return type.GetMethod(method);
 }
 
 public static void Main(string [] args)
 {
  DateTime start = DateTime.Now;
  MethodInfo method;
  method = GetMethod(typeof(DemoClass0),"DemoMethod0");
// ... 9998 more statements ... 

  method = GetMethod(typeof(DemoClass9999),"DemoMethod9999");
  Console.WriteLine("Took: {0}", DateTime.Now - start);
 } 

Here are the results.

Reflection .Net 2.0 & 1.1  218 - 187 Milliseconds
Static Delegates 125 Milliseconds

It's not a huge difference in the number, but it's something. And it's for 10,000 iterations. I was actually surpirsed that this happened. There doesn't seem to be any difference between 1.1 & 2.0 in this case (I imagine that it's not one that they optimized.) Using the static delegates took exactly 125 milliseconds each time I run it. It's not the performance that I'm looking at as the biggest advantage, it's the type safety in compile time.

Some things to note, there are two methods on the Static Reflection class, one for methods that has a return type, and one for those who don't. You can read the reason for that in Representing void methods with generic delegates.

The code (including the scripts I've used for generating the class and the benchmarks) are here. (You'll need Boo in order to run the benchmarks)

Enjoy,

Brail is Kicking one more :-)

For the last 20 days or so, Brail wasn't part of the Castle's build, beause of the changes in how testing is done. It took me some time, mainly because I couldn't really devote any real length time to this. So now Brail's tests support the Castle.MonoRail.TestSupport, and I can investigate some interesting changes that were made in both Boo and the view engine for MonoRail.

Type interference in .Net 2.0

I just opened SharpDevelop 2, and I got a killer Tip of the Day. You can do this:

? name = new SuperLongVariableName();

And the moment you hit the ; key, it will be replaced to:

SuperLongVariableName name = new SuperLongVariableName();

Now that is a useful feature. It doesn't work with Generics yet, but I really like the concept.

Tags:

Published at

Command line toys

I've a directory that contains a lot of sub-directoires with long names:

D:\Code\CastleProject\MonoRail>dir /ad/b Castle.*
Castle.MonoRail.ActiveRecordScaffold
Castle.MonoRail.ActiveRecordScaffold.Tests
Castle.MonoRail.ActiveRecordSupport
Castle.MonoRail.ActiveRecordSupport.Tests
Castle.MonoRail.Engine
Castle.MonoRail.Engine.Tests
Castle.MonoRail.Framework
Castle.MonoRail.Framework.Tests
Castle.MonoRail.Framework.Views.CompositeView
Castle.MonoRail.Framework.Views.NVelocity
Castle.MonoRail.Framework.Views.NVelocity.Tests
Castle.MonoRail.Generator
Castle.MonoRail.TestSupport
Castle.MonoRail.Views.Brail
Castle.MonoRail.Views.Brail.Tests
Castle.MonoRail.WindsorExtension
Castle.MonoRail.WindsorExtension.Tests

D:\Code\CastleProject\MonoRail>cd *br*

D:\Code\CastleProject\MonoRail\Castle.MonoRail.Views.Brail>

I knew that you can use * to tab to the correct directory, but it's nice to see cmd.exe guess my mind :-)

How to make people happy

[Via Alex Bendig] There is a post in The Business of Software about a new manager that came into the company:

... we got a new director of engineering.  During a meeting on why we were late on our current project and what could be done about it, I joked and said he could get us a fridge in the lab with Mt. Dew in it.  Well not an hour later, a guy comes in wheeling a mini fridge...stocked with Mt. Dew.

Personally, nothing has motivated me more to want to work in that dull white lab and put in some extra effort then that small [sic]jesture.

There were a lot of comments there saying that they should have asked for [Aeron Chair/Dual LCD monitors/more RAM/Etc], but some of the commenters there got the issue (Joel Spolsky & Eric Sink). It's not about what was requested, it wasn't a meeting to request stuff that they needed. It was a chance comment that the manager acted upon, and in one simple gesture, he gained quite a lot of good will and made sure that the entire department would like being there.

I'm currently reading Peopleware, and the whole book can be summarized as "Make them happy!" That manager made his people happy, period. It's a small investement, but it'll pay off big dividends later on. People who like what they doing are going to do it better.

Tags:

Published at

Cool Boo: Code Generation Inside the Languague

Here is one more case where an open compiler architecture is a huge plus.

At Mario has created an addin to Boo that gives you the ability to generate from insdie the lagnauge, this one has huge implications. I can see it as a cool way to create mixins. You code use it like this:

# blog.boo
[InjectCode(MixinCodeBuilder, "Mixins\Taggable.mixin")]
public class Blog:
    [Property] name as string

# Mixins\Taggalbe.mixin
Implements: ITaggable
Code:
  Tags:
      get:
          # get tags

The result of this operation is that the Blog class would implement the ITaggable interface, and all the methods and properties that are specified in the mixin files will be enetered into the Blog class during the compilation process.

This mean that you can (even fromt he Blog class, to treat it as if it implemented ITaggable.

Now that is cool

How to use the GC for caching...

Consider the following situation:

  1. You need to do a transformation on an object, which is potentially a very expansive operation.
  2. The result of the transformation is needed in several locations, and you've no way to control when/how they need it. (The object reside in a repository and can be requested by anyone).
  3. You want to use caching, but afraid of causing memory leaks or starting to get into trouble with expiration policies and managing the cache. (And you don't want / can't use Asp.Net's Cache).

What do you do?

I just had a situation like that, I needed to do a transformation on a object so I could run various rules on it (rules are my life recently ;-) ), the rules usually need some sort of a second graph to run over, which is pretty expansive. I didn't want to use the Asp.Net Cache because that introduce a dependecy that I can't control, and make it hard to test stuff. In addition, while hundreds of rule may require the result of the trasformation, it's only going to live for a relatively short period of time, in this case, caching would really be a waste of time.

The solution? Make the object its own cache, and rely on the GC cleaning up after you. Here is the implementation:

internal interface IExpensiveObjectHolder
{
    ExpensiveObject ExpensiveObject { get; set; }
}
public class ObjectGraph :  IExpensiveObjectHolder
{
   ExpensiveObject  expensive;
   ExpensiveObject IExpensiveObjectHolder.ExpensiveObject
   {
      get{ return expensive; }
      set{ expensive = value; }
   }
}
public class Transformer
{
    public ExpensiveObject Transform(ObjectGraph objGraph)
    {
         IExpensiveObjectHolder holder = (IExpensiveObjectHolder) objGraph;
         if(holder.ExpensiveObject!=null
                holder.ExpesiveObject = ExpensiveTransformation(objGraph);
         return holder.ExpesiveObject;
    }
}

Users of the class sees nothing, but the object is created once and only once, potentially saving quite a lot. Of course, you need to measure first, and you may run into problems where the object that request the cached object change it, which cause problem to other objects. If it's easier to clone the object than to create it, than it's one way to go.

Tags:

Published at

More on ReSharper 2.0 EAP

I'm currently testing build 208, I like it, but it still has its fair share of problems. It's an alpha, so it's to be expected.
The code formatter isn't there yet, there aren't enough options about anonymous delegates, which really bother me. You can't tell R# to do this:

Method(delegate(int i)
{
 return i*3;
}; 

Or this:

Method(delegate(int i) { return i*3; }; 

And it still has some problems with generics & such. I'm following the newsgroups, and the progress is impressive. Impressive enough that I'm using it for writing code despite the limitations, although I think that I'll have to remove this build in the short future if the rate of exceptions continue as is, and wait for the next one. One of the things that really bother me is that R# repaints the code, and I can find no way to disable just that. I hate what it does to variables and to break points. It got to the point when I tried to turn off highlighting, but then I also lost the smart code analysis features.

One killer feature that I found by accident was Ctrl+Shift+V, in VS, it's usually cycling through the paste buffer, but using R#, you get a dialog with the text in the paste buffer. This is so useful. Now I need to discover how to select them by using the keyboard...

Anyway, it's still alpha/pre-alpha, but damn it shows promise. Bugs that I've filed today are already fixed, for instance. And you can't compare 207 to 208 in terms of usability. I can't wait for the final version.

Update: Just to note, I'm using VS 2005 RC + ReSharper, so it's might be that that is causing all the problems. Since other people seems to have very good  experiance with the recent builds.

Tags:

Published at

Resharper 208

I’m now using the new build of ReSharper. I couldn’t get the previous build to work correctly, but this one is looking good for alpha software.

I just got an exception and a very nice message from R#, reporting that a previous problem that I reported have already been fixed.

Keep up the good work!

Published at

Enlightment?

Or maybe it is just me being stupid.

I had trouble recently with testing methods that return object graphs, the test would look like:

Assert.AreEqual(1, graph.Items[0].Count);

Assert.AreEqual(0, graph.Items[1].Count);

Assert.AreEqual(1, graph.Items[2].Count);

 

I just couldn’t stand the code, but I couldn’t think of a way to make it better, so I mostly tried to use Fit to do them, since it’s far more natural to do it this way.

Today I had to write another one of those tests, but this time I needed to do it in C#, and it just clicked, I can do this:

int [] expected = new int [] { 1,0,1};

for(int i=0;i<3;i++)
   Assert.AreEqual(expected[i[, graph.Items[i].Count, “Error on {0}”, i+1);

Now I’m feeling stupid that I didn’t figure it out in the first place.

 

 

Published at

Exceptional Exception

I just got an exception from my code, and I couldn’t be happier about it. I made a mistake a couple of days ago that cost me about half a day of trying to figure out what I did wrong. When I discovered what the problem was, I added an exception with a detailed message explaining the problem.

I just got this exception, after doing exactly the same mistake. I couldn’t be happier. It’s literally a one line change, and everything works as it should.

J

Published at

Coding & Music

I'm currently reading Peopleware, and I got to the part where they talk about background music and the affects this have about the right brain ability to generate insights.  I like to work while there is music in the background.  I think that I do it around 95% of the time. I usually tune it out after a while, but I'm told (I rarely notice it), that I sing along some of the songs.

I don't find that I've trouble coming up with solution while I'm working, but very often I've insights when doing something totally unrelated, like making coffee, driving, etc. I usually listen to music / podcasts while driving, so it might now be that. Then again, if I'm listening to a podcast, it's usually technical (unless it's Mondays), so it may be that the left brain is busy listening to that.

Any comments on that?

Tags:

Published at

Settings Repository

I just read Raymond Chen blog and one of the comments there threw me off.

 In my opinion, there should be a mechanism that allows you to do full-text search of "settings dialogs," so you don't have to know where to look. Kind of like searching the Control Panel in Windows Vista but even deeper. (Authors of such dialogs would have to opt-in and provide a reasonable entry point.)

I didn't know that the control panel could be searched (but not very well, apperantly. Searching for resolution didn't find anything.)

The important thing, however, is that the suggestion just make so much sense. We've seen it in about:config in firefox, and it's great. Having a central repository for all the settings for all the applications in the computer would be great. Being able to search it would be fantastic.

Generic firing of asynchoronous events

Tomer Gabel had a problem with raising events from outside the class that declared them. The problem was compounded by the need to process the event asynchoronously, since System.Delegate doesn't implement BeginInvoke().

His solution was to use Reflection to do that. I was interested enough to investigate whatever I could do that without Reflection. I was surprised to learn that BeginInvoke was only available on specialized delegates. My solution is based around this article: Calling Synchronous Methods Asynchronously which truly proves that you can solve anything in computers by putting another layer of indirection, except too many layers of indirection :-)

Since Tomer seems to want a contain to hold many events, I decided that I would skip events and go straight to the source, delegates. The container class that I've here has a list of registered events and list of subscribers. Subscribing to a non-existing method creates it. Firing events can be done in three modes:

  • Totally synchoronously, which means that you wait until all the subscribers have finished.
  • Asynchoronously firing the event, but each subscriber is processed in turn.
  • Asynchoronously firing the event, each subscriber is proccessed asynchoronously.

Note: Exceptions during calls is a tricky subject, I tried to give as much information as possible, but I've no idea how to get an exception thrown from async method.

There is no reflection there, but a lot of indirection, the whole thing is based around the concept that while you may not be able to run a Delegate asyncrously, you can run a method asyncrously that would run the delegate. If you managed to follow the last sentence, I applaud you.

private delegate void DynamicInvoke(Delegate d, object[] args);

public
 void BeginDynamicInvoke(Delegate call, object[] args, EventHandler callback)
{
 this.fireSignle = new DynamicInvoke(CallDynamicInvoke);
 this.fireSignle.BeginInvoke(call, args, new AsyncCallback(AsyncCallback), null);
}

private
 void CallDynamicInvoke(Delegate d, object[] args)
{
 d.DynamicInvoke(args);
}

It's too big to post fully on the blog, you can find it here.

Unsubscribing and deleting events are left as an exercise to the reader :-)

Tags:

Published at

Spam Blogs become Google Problem

Joel has a very interesting article about the reasoning behind spam blogs. As always, it's money that is the cause, but this time the "victim" is Google. And that gives me some hope that it will stop.

A spam blog is usually just an aggeration of links from other sites which usually has a Google AdSense account. Then a zombie network is used to click on the links, and the spammers colleccts the profits. The interesting thing here is that Google is both the target and the supplier here, since BlogSpot, which it own, is one of the bigger targets for scripted-blog-generation. Google is losing money because of this, and that gives them a pretty strong incentive to stop that. Further more, they are losing the trust of the advertisiers. If I know that paying for AdSense would get me 50% visits from some spammer, I'm really going to consider whatever to pay for that or not...

Tags:

Published at

Sharp Develop 2.0

I've had a fairly strong negative reaction to #Develop in the past, mostly because you really couldn't use if without the IDE started to throw exceptions and refusing to work (my pet peeve was the inability to create & rename new folders, an issue that often require a manual editing of the project file.)

I got a couple of recommendation about #Develop 2, which is currently only available from the Subversion repository (svn://sharpdevelop.net/corsavy/trunk) (or so I understand). I'm writing this review based on revision 619.

Over all, I've got a good feeling about it. The interface is much more proffesional, and it has a built in Boo.

Some things that I didn't like:

  • Over eager highlighting and intellisense. It should give me a couple hundred milli seconds of wait before doing that, moving the cursor over the code is like watching fireworks :-)
  • Search & Replace has improved tremendously, but I couldn't figure out how to do S&R using regular expressiong with replacing tagged text. In VS.Net, I can use:
    seach text: url = {"[^"]+"}
    replace with: DoGet(\1)
    And it would replace url = "something" to DoGet("something")

The #Develop team has a blog here.

Tags:

Published at

C# .0 Code Formatter

Does anyone knows of a C# 2.0 code formatter that is available now? I want something just like what ReSharper has for 1.1, where you get tons of options for just about anything.

Spesifically, I need something that will understand how to handle anonymous delegates. Right now R# doesn't handle them very well. My current code style is:

//one liner
sw.Action = delegate(ISalesContext sales) { return sales.Items.Count;}

//multi line

sw.Action = delegate(ISalesContext sales)
{
    if(sales.Date.AddMonths(1) > DateTime.Month)
        return 0;
    return sales.Items.Count;
}
Tags:

Published at

Does Visual Studio Rot the Mind?

Charles Petzold has a really good talk here about the problems that VS.Net gets you with being too smart. Best quote:

IntelliSense is considered by some to be the most important programming innovation since caffeine

I tend to agree with him, although I come to it from a different angle. From a non UI perspective, I find that people often takes the easy path that they saw on the demos, and very quickly ends up with unmaintainable mess. I'm a big believer in making the computer work for me, but I like to do it using abstractions and code, not via wizard magic.