Skip to main content

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.

Comments

Popular posts from this blog

async.h - asynchronous, stackless subroutines in C

The async/await idiom is becoming increasingly popular. The first widely used language to include it was C#, and it has now spread into JavaScript and Rust. Now C/C++ programmers don't have to feel left out, because async.h is a header-only library that brings async/await to C! Features: It's 100% portable C. It requires very little state (2 bytes). It's not dependent on an OS. It's a bit simpler to understand than protothreads because the async state is caller-saved rather than callee-saved. #include "async.h" struct async pt; struct timer timer; async example(struct async *pt) { async_begin(pt); while(1) { if(initiate_io()) { timer_start(&timer); await(io_completed() || timer_expired(&timer)); read_data(); } } async_end; } This library is basically a modified version of the idioms found in the Protothreads library by Adam Dunkels, so it's not truly ground bre

Easy Automatic Differentiation in C#

I've recently been researching optimization and automatic differentiation (AD) , and decided to take a crack at distilling its essence in C#. Note that automatic differentiation (AD) is different than numerical differentiation . Math.NET already provides excellent support for numerical differentiation . C# doesn't seem to have many options for automatic differentiation, consisting mainly of an F# library with an interop layer, or paid libraries . Neither of these are suitable for learning how AD works. So here's a simple C# implementation of AD that relies on only two things: C#'s operator overloading, and arrays to represent the derivatives, which I think makes it pretty easy to understand. It's not particularly efficient, but it's simple! See the "Optimizations" section at the end if you want a very efficient specialization of this technique. What is Automatic Differentiation? Simply put, automatic differentiation is a technique for calcu

Building a Query DSL in C#

I recently built a REST API prototype where one of the endpoints accepted a string representing a filter to apply to a set of results. For instance, for entities with named properties "Foo" and "Bar", a string like "(Foo = 'some string') or (Bar > 99)" would filter out the results where either Bar is less than or equal to 99, or Foo is not "some string". This would translate pretty straightforwardly into a SQL query, but as a masochist I was set on using Google Datastore as the backend, which unfortunately has a limited filtering API : It does not support disjunctions, ie. "OR" clauses. It does not support filtering using inequalities on more than one property. It does not support a not-equal operation. So in this post, I will describe the design which achieves the following goals: A backend-agnostic querying API supporting arbitrary clauses, conjunctions ("AND"), and disjunctions ("OR"). Implemen