Showing posts with label code. Show all posts
Showing posts with label code. Show all posts

Tuesday, June 3, 2008

Javascript Dom Builder

I hate the Dom. So I decided to fix it.

basic usage



1 domBuilder($get('basic'))
2 .div({id: 'div1', style: 'height:50px;border-style:solid;border-width:4px;padding:1px;'})
3 .div({style: 'float: left;' })
4 .span().text('purple, ').end()
5 .a({href: 'http://www.google.com'}).text('google').end()
6 .end()
7 .div({style: 'float: right;' })
8 .span().text('cat').end()
9 .end();


Adding event handlers



1 domBuilder($get('event'))
2 .div()
3 .span().text('some text').end()
4 .input({id:'evt_test', type: 'text'})
5 .on('blur', function() {alert('blur!!');})
6 .on('focus', function() {alert('focus!')})
7 .end();


using custom (not pre-defined) tags


30 domBuilder($get('custom'), ['select', 'optgroup', 'option'])
31 .select({id: 'select_test'})
32 .optgroup({label: 'swedish cars'})
33 .option({value: 'v'}).text('Volvo').end()
34 .option({value: 's'}).text('Saab').end()
35 .option({value: ''}).text('').end()
36 .end()
37 .optgroup({label: 'german cars'})
38 .option({value: 'm'}).text('Mercedes').end()
39 .option({value: 'a'}).text('Audi').end()
40 .end();

Essentially the DomBuilder function takes a dom element and returns an object that lets you add nodes to it easily. The most common html tags are pre-defined. You can also insert an arbitrary tag with the 'add' method: .add('foo', {id: 'foo'}, although the expected usage is to tell dom-builder about the custom tags you want to use and let it create helper methods for you, as in the third example above. One gotcha of the fluent-interface style is that you may be tempted to leave off the end() calls - proper indentation can make the code look correct when it is not. end() is only optional at the end of the method chain - in other words, final end()s are implicit.

One benefit of the fluent interface style is that it is easy to extend. For example, you could add a css() method, some sort of auto-close method or parameter (analagous to <tag />) so that an explicit end() is not required, etc.

dom-builder is built on top of the YUI library, and is inspired slightly by Ruby's xml-builder. It is not an original idea - it has been done before in similar ways and by smarter people. Still, it's always helpful to work something out for yourself, and from an aesthetic point of view I prefer my API decisions over those solutions anyway. Examples, tests, and source are available here.

Monday, December 24, 2007

Encapsulating the Lazy-Load Pattern

In a previous post I suggested a helper method to wrap the Lazy-Load pattern. I'm now convinced that this was the wrong approach to take. For the specific case I mentioned (lazy-loading a property value), the straightforward test for nil is irreducibly simple. Trying to wrap it in a generic procedure introduces more complexity than it alleviates.

For the more general case in which you have a value which you don't want to compute more than once, the correct pattern is to create a generic wrapper class or method which bolts added functionality onto the type in question. This is a common pattern in the .Net framework: some examples are Nullable<T>, Expression<T>, and Future<T> in the next version of Chrome.

So what we want is some sort of Lazy which will wrap the lazy evaluation. There is one example implementation here. You can consult the article for the (mostly trivial) implementation details. But it enables you to write code like this:


    1 int a = 22, b = 20;

    2 var lazy = Lazy.New(() => {

    3     Console.WriteLine("calculating...");

    4     return new { Mul = a*b, Sum = a+b };

    5 });

    6 Console.WriteLine("Mul = {0}, Sum = {1}",

    7 lazy.Value.Mul, lazy.Value.Sum);


The block passed to Lazy.New is evaluated only once, in the first call to lazy.value. The Lazy.New function is a helper which basically exists to trick the compiler into providing type inference for us so that we don't have to specify the generic parameter.

The main downside to this is that Lazy<T> is a custom class. In order for a function to accept one of these lazy objects as a parameter, it needs to be written specifically with that type in mind. Wouldn't it be cleaner if Lazy.New returned an Func<T> delegate instead?

Well, it can. What we basically want to do is memoize a function of no arguments. And for that the solution presented here is much cleaner:


    1 static class FuncLib

    2 {

    3     public static Func<R> Memoize<R>(this Func<R> f)

    4     {

    5         R value = default(R);

    6         bool hasValue = false;

    7         return () =>

    8         {

    9             if (!hasValue)

   10             {

   11                 hasValue = true;

   12                 value = f();

   13             }

   14             return value;

   15         };

   16     }

   17 }


Let's walk through this. Memoize<T> takes a function with no parameters, and returns a function of the same signature. Here's where it gets sneaky: whereas Lazy<T> defined a new class to hold the cached function value, Memoize<T> takes advantage of the fact that closures have access to their containing scope. The return function carries around a pointer to the local variables value and hasValue, which allows it to remember if the result has been calculated or not.

As an added bonus Memoize<T> is an extension method, which allows us to use it in two ways: either as a helper method similar to Lazy.New, or as a method on a Func<T> object. We can then create lazily-evaluated functions like so:


   32 //using Memoize as a helper method, with type inference

   33 var lazy = FuncLib.Memoize( () =>

   34 {

   35     Console.WriteLine( "calculating..." );

   36     return 42;

   37 } );

   38 

   39 //using Memoize as an extension method,

   40 //  but without type inference

   41 Func<int> lazy2 = ( () =>

   42 {

   43     Console.WriteLine( "calculating..." );

   44     return 42;

   45 } );

   46 lazy2 = lazy2.Memoize();

   47 

   48 Console.WriteLine( "starting..." );

   49 Console.WriteLine( "result = {0}", lazy() );

   50 Console.WriteLine( "result (again) = {0}", lazy() );

   51 Console.Read();


This can't be beaten for elegance, in my opinion.

Monday, October 22, 2007

Parsing Enterprise Library Trace Output in Ruby

The Enterprise Library Tracer class is really handy, and I use it all the time for profiling slow pieces of code. The only problem is that its output is illegible. Using the default formatter, this is what one trace looks like:

    1 ----------------------------------------
    2 Timestamp: 10/24/2007 12:42:00 AM
    3 Message: Start Trace: Activity '03952c36-9e3c-4175-bd5a-9f2ea108ae7e' in method 'App.Results.LoadData' at 1981206585161 ticks
    4 Category: GetPhotoQuery
    5 Priority: 5
    6 EventId: 1
    7 Severity: Start
    8 Title:TracerEnter
    9 Machine: GABE-THINKPAD
   10 Application Domain: /LM/W3SVC/1/Root/App-11-128376599444062500
   11 Process Id: 3564
   12 Process Name: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_wp.exe
   13 Win32 Thread Id: 4164
   14 Thread Name: 
   15 Extended Properties: 
   16 ----------------------------------------
   17 ----------------------------------------
   18 Timestamp: 10/24/2007 12:42:09 AM
   19 Message: End Trace: Activity 'b7b7c71f-30ae-486c-8206-4d87dd44e2fc' in method 'App.Results.LoadData' at 1981236796826 ticks (elapsed time: 0.07 seconds)
   20 Category: GetPhotoQuery
   21 Priority: 5
   22 EventId: 1
   23 Severity: Stop
   24 Title:TracerExit
   25 Machine: GABE-THINKPAD
   26 Application Domain: /LM/W3SVC/1/Root/App-11-128376599444062500
   27 Process Id: 3564
   28 Process Name: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_wp.exe
   29 Win32 Thread Id: 4164
   30 Thread Name: 
   31 Extended Properties: 
   32 ----------------------------------------

If you are using multiple nested traces, it's impossible to interpret. A custom log formatter can help.

    1 <formatters>
    2   ...
    3   <add template="{category}({timestamp}): {message}" 
    4     type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
    5     name="Short Text Formatter" />
    6 </formatters>

Each entry is now compressed onto one line, and I've gotten rid of the information I don't need:

    1 GetPhotoQuery(8/24/2007 12:16:48 AM): Start Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.Results.LoadData' at 2228474435074 ticks
    2 GetRecords, GetPhotoQuery(8/24/2007 12:16:48 AM): Start Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.DataLayer.getPhotoQuery' at 2228474857791 ticks
    3 GetRecords, GetPhotoQuery(8/24/2007 12:16:48 AM): End Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.DataLayer.getPhotoQuery' at 2228475263799 ticks (elapsed time: 0.113 seconds)
    4 CreatePhotoRecList, GetPhotoQuery(8/24/2007 12:16:48 AM): Start Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.DataLayer.getPhotoQuery' at 2228475271352 ticks
    5 LoadAdditionalImages, CreatePhotoRecList, GetPhotoQuery(8/24/2007 12:16:48 AM): Start Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.PhotoRecList..ctor' at 2228475285334 ticks
    6 LoadAdditionalImages, CreatePhotoRecList, GetPhotoQuery(8/24/2007 12:16:48 AM): End Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.PhotoRecList..ctor' at 2228475413732 ticks (elapsed time: 0.035 seconds)
    7 LoadPhotoList, CreatePhotoRecList, GetPhotoQuery(8/24/2007 12:16:48 AM): Start Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.PhotoRecList..ctor' at 2228475416500 ticks
    8 LoadPhotoList, CreatePhotoRecList, GetPhotoQuery(8/24/2007 12:16:52 AM): End Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.PhotoRecList..ctor' at 2228491499906 ticks (elapsed time: 4.493 seconds)
    9 SortingResults, CreatePhotoRecList, GetPhotoQuery(8/24/2007 12:16:52 AM): Start Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.PhotoRecList..ctor' at 2228491505534 ticks
   10 SortingResults, CreatePhotoRecList, GetPhotoQuery(8/24/2007 12:16:52 AM): End Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.PhotoRecList..ctor' at 2228491518346 ticks (elapsed time: 0.003 seconds)
   11 CreatePhotoRecList, GetPhotoQuery(8/24/2007 12:16:52 AM): End Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.DataLayer.getPhotoQuery' at 2228491520681 ticks (elapsed time: 4.539 seconds)
   12 GetPhotoQuery(8/24/2007 12:16:52 AM): End Trace: Activity '466569a2-9713-4e66-aeca-d7d9f87aaae5' in method 'App.Results.LoadData' at 2228491521707 ticks (elapsed time: 4.773 seconds)

Better, but still hard to interpret. If you squint you can tell that this is a series of nested traces. Execution time is at the end of the line. I used to be in the habit of manually reformatting these files: adding indentation to show nesting and removing the attributes I didn't care about. But this kind of busywork is the problem computers were invented to solve. 37 lines of Ruby later, I had a script which would parse trace output like so:

    1 GetPhotoQuery
    2     GetRecords
    3     GetRecords completed at 8/24/2007 12:16:48 AM (0.113 seconds elapsed)
    4     CreatePhotoRecList
    5         LoadAdditionalImages
    6         LoadAdditionalImages completed at 8/24/2007 12:16:48 AM (0.035 seconds elapsed)
    7         LoadPhotoList
    8         LoadPhotoList completed at 8/24/2007 12:16:52 AM (4.493 seconds elapsed)
    9         SortingResults
   10         SortingResults completed at 8/24/2007 12:16:52 AM (0.003 seconds elapsed)
   11     CreatePhotoRecList completed at 8/24/2007 12:16:52 AM (4.539 seconds elapsed)
   12 GetPhotoQuery completed at 8/24/2007 12:16:52 AM (4.773 seconds elapsed)

Which shows me exactly where the bottleneck is. Some highlights of the script:
  • It chooses the file to parse based on the the script's own filename. Just copy it to the directory your trace file is in and rename it to, eg., mytraces.log.trace_parser.rb to parse the file mytraces.log. This piece of convention over configuration was inspired by Rails.
  • It generates the readable trace and automatically opens it in notepad for you, so getting the parsed output is a one-step process.
The Ruby source looks like this:

    1 #outputs a file with the trace information in a more readable format
    2 
    3 #convention: the log filename is the script filename minus
    4 #   the trailing name and rb extension 
    5 src_filename = __FILE__.chomp ".trace_parser.rb"
    6 dest_filename = "#{src_filename}.readable"
    7 dest = File.open(dest_filename, "w")
    8 
    9 File.open src_filename do |src|
   10   #need to keep track of how deeply nested we are
   11   indent_level = -1
   12 
   13   #print a line with the correct indent
   14   print_line = lambda { |s| dest.puts(" " * 4 * indent_level + s) }
   15 
   16   while line = src.gets do
   17     ##### try to capture the start of a trace #####
   18     _, name = /^(\w+).*\Start\sTrace/.match(line).to_a
   19     if name then
   20       indent_level += 1
   21       print_line.call(name)
   22       next # move along to the next line
   23     end
   24 
   25     ##### try to capture the end of a trace #####
   26     _, name, time, sec = /^(\w+).*\((.*)\).*End\sTrace.*elapsed time: (\d+\.?\d*) seconds/.match(line).to_a 
   27     if name then
   28       print_line.call("#{name} completed at #{time} (#{sec} seconds elapsed)")
   29       indent_level -= 1
   30       next # move along to the next line
   31     end
   32   end
   33 end
   34 
   35 dest.close
   36 
   37 `notepad #{dest_filename}`

Tuesday, October 9, 2007

YCombinator in Chrome

When Chrome 2.0 was released I spent some time playing with its new support for anonymous methods. One of the things I did was to derive the Y Combinator, which lets you generate recursive anonymous methods.

Anyway, I wrote a series of blog posts about the experience over on the company blog. Low on usefulness, but high on bizarre, esoteric, and fun! Enjoy:

Anonymous Methods in Chrome

Y in Chrome Part 1: Currying

Y in Chrome Part 2

Y in Chrome Part 3