Saturday, June 8, 2013

Sasa Wrap-Up - Lazy<T>, Value, and Dictionary Extensions

This is the twenty third post in my ongoing series covering the abstractions in Sasa. Previous posts:

After this post, I will have covered everything in the core Sasa assembly, Sasa.dll. The first two posts covered Sasa.Parsing.dll and Sasa.Dynamics.dll, and now that the core assembly has been covered, I have only a few final cleanups of the codebase before I release v0.9.4 final on Sourceforge and Nuget.

Of course, there still remains Sasa.Concurrency, Sasa.Binary, Sasa.Collections, Sasa.Numerics, Sasa.Linq, Sasa.Mime, Sasa.Net, and Sasa.Reactive to document, at the very least. The world of Sasa is far larger than what's been covered so far, so there's plenty left to explore! I will continue to write periodic blog posts or other sorts of documentation covering these assemblies, but I won't hold up the v0.9.4 release any further.

Sasa.Lazy<T>

.NET 4.0 was released with a Lazy<T> type, although the one in Sasa predates this one by quite a bit and is somewhat simpler. In principle, there is little difference between a Lazy<T>, a Task<T>/Future<T> and a reactive value, React<T>, like that found in the Sasa.Reactive assembly. In fact, the latter largely generalizes the semantics of the previous three since you can easily register for notifications and construct chained computations ala promise pipelining. As such, Sasa.Lazy<T> may one day be replaced by React<T> or something even more general. But it's available in the meantime, it's simple, and it's well tested in production environments.

Sasa.Lazy<T> Interfaces

The lazy type implements the following core interfaces: IValue<T>, IOptional<T>, IResolvable<T>, IVolatile<T>, and IResult<T>. To summarize, this set of interfaces exports the following methods and properties:

// starts the lazy computation, if not already
// run, and returns the computed value
T IRef<T>.Value { get; }

// returns false if the computation has not yet started
// or it returned an error of some kind
bool IResolvable<T>.HasValue { get; }

// returns false if the computation has not yet started
// or it returned an error of some kind and sets
// 'value' to the encapsulated value
bool IVolatile<T>.TryGetValue(out T value);

// provides the exception generated by the lazy computation
// if any was generated; if an exception was generated, then
// HasValue and TryGetValue both return false
Exception IResult<T>.Error { get; }

Sasa.Lazy<T> Constructors

The lazy constructors either accept a function describing the lazy computation, or accept a value to construct an already resolved lazy type. These constructors are also defined as implicit conversions:

Lazy<int> x = 3;
Lazy<int> y = new Lazy<int>(() =>
{
  Console.WriteLine("Please enter a number:");
  return int.Parse(Console.ReadLine());
});
Console.WriteLine(x.HasValue);
Console.WriteLine(y.HasValue);
// output:
// true
// false

Sasa.Lazy.Create

Lazy.Create are an overloaded set of static methods used to construct lazy types, somewhat akin to the constructors:

Lazy<int> x = Lazy.Create(3);
Lazy<int> y = Lazy.Create(() =>
{
  Console.WriteLine("Please enter a number:");
  return int.Parse(Console.ReadLine());
});
Console.WriteLine(x.HasValue);
Console.WriteLine(y.HasValue);
// output:
// true
// false

Sasa.Values

Sasa.Values is a static class intended to encapsulate a set of useful methods defined for all values. It currently exports only a single overloaded extension method.

Sasa.Values.IsIn

Sasa.Values.IsIn is an overloaded extension method used to check if a value is contained within a collection of other items. Logically, it's equivalent to SQL's "IN" operator. In it's most general form, IsIn is a shorthand for Enumerable.Contains, but the more specific overloads are far more efficient and don't require the construction of a collection to check membership:

Console.WriteLine(3.IsIn(1,2,3,4));
Console.WriteLine("xxx".IsIn("hello", "world!"));
// output:
// true
// false

As explained before, you can already perform this check in standard C#, but it's far more verbose. You either have to create a switch statement, or a set of logical conjunctions in an if-statement, or you have to construct a collection and call Contains like so:

Console.WriteLine(new[] { 1,2,3,4 }.Contains(3));
Console.WriteLine(new[] { "hello", "world!" }.Contains("xxx"));
// output:
// true
// false

Using the IsIn extension is far more concise, and I'd argue, much clearer. A set of overloads accepting an IEqualityComparer.

Sasa.Collections.Dictionaries

The Sasa.Collections.Dictionaries static class exports a small set of extension methods on IDictionary<TKey, TValue>.

Sasa.Collections.Dictionaries.FindOrDefault

Dictionaries.FindOrDefault checks for the presence of a key, and if not present, returns Option<TValue>.None:

var dict = new Dictionary<string, int>
{
  { "foo",          0 },
  { "hello world!", 9 },
};
var hasBar = dict.FindOrDefault("bar")
          || 666;
Console.WriteLine(hasBar);
// output:
// 666

Sasa.Collections.Dictionaries.InsertDefault

Dictionaries.InsertDefault is a set of extension methods that inserts a default value only if the key is currently unbound:

var dict = new Dictionary<string, int>
{
  { "foo",          0 },
  { "hello world!", 9 },
};
dict.InsertDefault("bar", 666);
dict.InsertDefault("foo", () => 1234);

Console.WriteLine(dict["foo"]);
Console.WriteLine(dict["bar"]);
// output:
// 0
// 666

As you can see above, there are two overloads, one accepting a simple value, and one accepting a function that produces a value for the cases where the default value may be expensive to produce.

Note that there may be a few more abstractions or extension methods in the core Sasa dll that weren't entirely documented in this series, but these are abstractions whose API isn't entirely satisfactory, and may soon be refactored or removed. For instance, this is the case with Dictionaries.FindOrOtherwise, which has been in Sasa for quite awhile, and which I've used in some production code, but will probably be replace by the more genreal FindOrDefault.

No comments: