Monday, August 30, 2010

HyperJS Episode 1 - The Phantom Project

JavaScript in C# starts with Prototype Inheritance

Posts in the HyperJS Series

Last weekend, I worked on my first Github projects: HyperCore, HyperJS, and HyperActive (I like "Hyper" as my project prefix since my name is pronounced "High-pull"). You can find them at: http://github.com/tchype.

In part 1, I'll be covering my original free time prototyping that, a week later, has resulted in the start of something I find very interesting and fun, even if it ends up being pointless...JavaScript-style coding in C#. That first step is something I call HyperDictionary

HyperJS Part 1: HyperDictionary

The original goal of my free-time project was to create a Dictionary of key/value pairs that supports inheritance and overriding of values--a sort of HyperDictionary since it exists in multiple dimensions once you include the ancestors. This had come out of some challenges my friends were facing at work with dealing with customer contracts that had sub-contracts that mostly followed the main contract but had some changes per sub-contract. I wasn't working directly on it myself but I had to scratch that itch at home...

I created a HyperDictionary class that implements IDictionary<string, object>. It allows you to specify a "parent" IDictionary<string, object>. It also allows you to Add, Remove, or Extend values based on their keys and types. You can Add or Remove any key from the dictionary and you can Extend any IEnumerable.

I was inspired by two things:

  1. ASP.NET Web.config: The AppSettings section lets you add and remove key/value pairs at the machine level, your web site level, or a particular folder within a web site, always referring back to the higher level to find settings.
  2. JavaScript: Prototype inheritance seemed to be the way to go, where you can see if a dictionary "HasOwnProperty(name)" and if not, asking for a key will then check it's parent to see if it has it, and so on.

Examples

In this first example, we simply define a top-level dictionary--top--and a second level dictionary--second--where one property is only defined in top and the other is overridden in second:

Console.WriteLine("Using HyperDictionary to show dictionary inheritance\n==================================================");
var top = new HyperDictionary("top");
top["eyes"] = "brown";
top["hair"] = "pointy";

var second = new HyperDictionary("second");
second.InheritsFrom(top);
second["hair"] = "straight";

Console.WriteLine("top[\"eyes\"]:\t{0}", top["eyes"]);
Console.WriteLine("top[\"hair\"]:\t{0}", top["hair"]);
Console.WriteLine("second[\"eyes\"]:\t{0}", second["eyes"]);
Console.WriteLine("second[\"hair\"]:\t{0}", second["hair"]);

Which outputs:

Using HyperDictionary to show dictionary inheritance
==================================================
top["eyes"]:    brown
top["hair"]:    pointy
second["eyes"]: brown
second["hair"]: straight

This works n-levels deep as well. Here's an example where I removed a setting and extended an IEnumerable as well:

//Extends and removes using an IEnumerable<object> value
top["things"] = new string[] { "first thing", "second thing" };

var third = new HyperDictionary("third");
third.InheritsFrom(second);
third.RemoveProperty("hair");
third.ExtendProperty("things", new object[] { 3, 4, 5 });

//Output members of third - note the absence of "hair" member
Console.Write("third members:\n");
foreach (object o in third)
{
    Console.WriteLine(o);
}
Console.WriteLine();

// Output the extended list of items in "things", 
// some from top and some from third.
// And notice: DIFFERENT DATA TYPES!
Console.Write("third things:\t");
var things = third["things"] as IEnumerable<object>;
foreach (object thing in things)
{
    Console.Write(" | " + thing.ToString());
}
Console.Write(" | ");
Console.WriteLine();

Which outputs:

third members:
[things, System.Linq.Enumerable+<UnionIterator>d__88`1[System.Object]]
[eyes, brown]

third things:    | first thing | second thing | 3 | 4 | 5 |

Potential Uses

Here's a couple quick ideas for uses of the HyperDictionary:

  • Versioned object serialization: Each version of the object is it's own HyperDictionary that "inherits" from the previous version and it's own KeyValuePairs the deltas. You can store the Tuples as the values (PropertyActions are Add, Remove, Extend) for each version and easily recreate point-in-time versions. You could also store the "current" full dictionary (including ancestor key/value pairs) separately for speed since going back versions is usually a special-case. This works fairly easily with both a no-SQL and a SQL datastore.
  • In-memory data changes: You can read key/value pairs out of a data store (database, cookie, etc.) into a "current settings" dictionary. Then, create a "user changes" dictionary with the "current settings" as the parent. When the user makes changes, it updates the "current settings" dictionary and then you can save the "user settings" dictionary back to the data store, using the proper Add, Remove, or Extend action. Create the "Default Settings" as the top-level dictionary and you can enable "Restore Defaults" very easily.
  • Hierarchical configuration: It was partially based on this concept, should work...
  • Role-based security: You could implement a base set of entitlements that are set to some default key/value pairs (e.g., "CanUseSystem" => true, "CanAuthorizePayments" => false, "CanCreateUsers" => false) and then, based on the user's role or particular account, add or remove or extend their capabilities using a new dictionary at each level.

Next Up: JavaScript Style

In the next post, I'll describe how I wanted to be able to set items using the indexer notation and access that value via a dynamic property reference:

foo["bar"] = "baz";
var gotBar = foo.bar;
From there, it ends up being an easy cognitive jump to JavaScript style (and, you get to use closures in cool ways)! It also ends up to not be an easy jump to implement it "for reals."

HyperDictionary is part of my HyperCore GitHub project that also includes the capabilities I'll describe in the next two posts. http://github.com/tchype

No comments: