This is the twenty third post in my ongoing series covering the abstractions in Sasa. Previous posts:
- Sasa.Parsing - type-safe, extensible lexing and parsing framework
- Sasa.Dynamics - type-safe polytypic/reflective programming
- Sasa.Func - Type-Safe Delegate Combinators
- Sasa.Option - Handling Optional Values
- Sasa.Result - Handling Exceptional Values
- Sasa.Numbers - Generic Number Extensions
- Sasa.Strings - General String Extensions
- Sasa.Types - Runtime Types And CLR Metadata
- Sasa.Weak - Typed Weak References
- Sasa's Tuples
- Sasa's Core Interfaces
- Sasa.Events - Type-Safe, Null-Safe, Thread-Safe Events
- Sasa.Web.Url64 - URL-Safe Base64 Encoding
- Sasa.Operators<T> - Generic Arithmetic and Logical Operators
- Sasa.IO.FilePath - Easy and Safe Path Manipulations
- Sasa.IO.Streams - Convenient Stream Extensions
- Sasa.Linq.Enumerables - Extensions on IEnumerable<T>
- Sasa.Either - Simple Sums for .NET
- Sasa.Atomics - Simpler, More Scalable Atomic Operations
- Sasa.Collections.Arrays - Purely Functional Array Combinators
- Sasa.IO.DisposableFile - Simple Temporary File Handling
- Sasa.TypeConstraint and IL Rewriting - Generic Constraints (No Longer) Forbidden in C#
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