This is the fourteenth 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
When writing generic code, it's pretty common to yearn for some generic arithmetic interface so you don't have to copy-paste the same function over and over to provide overloads for all the CLR's numeric types. Omitting a standard arithmetic interface was one of .NET's biggest mistakes in my opinion.
Enter Sasa.Operators<T>, Sasa.Operators<T0, T1>, and Sasa.Operators<T0, T1, T2>, which are static classes that expose delegates for all operators on any given types T, T0, T1, and T2. T must contain operator overloads for the corresponding operation to be available, or if T is a primitive type like Int32 or Double, then a delegate is dynamically generated and cached. If T does not contain operator overloads for a given operation, the corresponding delegate for that operation will be null, so you just need to check for their presence as a precondition.
Sasa.Operators<T>
Sasa.Operators<T> provides operators over a single generic type T. In other words, any operators defined only on a single type T will be available via this generic static class, eg. addition, subtraction, multiplication, etc.
Sasa.Operators<T>.Add
The standard addition operation, Sasa.Operators<T>.Add, is straightforward, taking two T arguments and returning their addition:
var x = Operators<int>.Add(1, 2); Console.WriteLine(x); // output: // 3
Sasa.Operators<T>.And
The Sasa.Operators<T>.And operation takes two T arguments, and returns a T argument which is the bitwise/logical 'and' of the two arguments:
var x = Operators<int>.And(1, 3); Console.WriteLine(x); // output: // 1
Sasa.Operators<T>.Decrement
The Sasa.Operators<T>.Decrement operation takes a T argument, and returns a "decremented" T argument:
var x = Operators<double>.Decrement(1.5); Console.WriteLine(x); // output: // 0.5
Sasa.Operators<T>.Divide
The Sasa.Operators<T>.Divide operation two T arguments and returns a T argument corresponding to their division:
var x = Operators<int>.Divide(6, 2); Console.WriteLine(x); // output: // 3
Sasa.Operators<T>.Equal
The Sasa.Operators<T>.Equal operation takes two T arguments and returns a bool argument corresponding to their equality:
var x = Operators<int>.Equal(6, 2); Console.WriteLine(x); // output: // false
Sasa.Operators<T>.False
The Sasa.Operators<T>.False operation takes a T argument and returns a bool based on whether the argument corresponds to that T's false value:
Console.WriteLine(Operators<bool>.False(false)); Console.WriteLine(Operators<bool>.False(true)); // output: // true // false
Sasa.Operators<T>.GreaterThan
The Sasa.Operators<T>.GreaterThan operation takes two T arguments and returns a bool based on whether the first argument is greater than the second:
var x = Operators<int>.GreaterThan(1, 2); Console.WriteLine(x); // output: // false
Sasa.Operators<T>.GreaterThanOrEqual
The Sasa.Operators<T>.GreaterThanOrEqual operation takes two T arguments and returns a bool based on whether the first argument is greater than or equal to the second:
var x = Operators<int>.GreaterThanOrEqual(1, 2); Console.WriteLine(x); // output: // false
Sasa.Operators<T>.Increment
The Sasa.Operators<T>.Increment operation a T argument and returns an "incremented" T:
var x = Operators<int>.Increment(98); Console.WriteLine(x); // output: // 99
Sasa.Operators<T>.LeftShift
The Sasa.Operators<T>.LeftShift operation a T argument, followed by an Int32 argument, and returns a T "left-shifted" by the Int32:
var x = Operators<int>.LeftShift(1, 3); Console.WriteLine(x); // output: // 8
Sasa.Operators<T>.LessThan
The Sasa.Operators<T>.LessThan operation takes two T arguments and returns a bool based on whether the first argument is less than the second:
var x = Operators<int>.LessThan(1, 2); Console.WriteLine(x); // output: // true
Sasa.Operators<T>.LessThanOrEqual
The Sasa.Operators<T>.GreaterThanOrEqual operation takes two T arguments and returns a bool based on whether the first argument is less than or equal to the second:
var x = Operators<int>.LessThanOrEqual(1, 2); Console.WriteLine(x); // output: // true
Sasa.Operators<T>.Modulo
The Sasa.Operators<T>.Modulo operation takes two T arguments, and returns the modulus/remainder of dividing the two arguments:
var x = Operators<int>.Modulo(9, 2); Console.WriteLine(x); // output: // 1
Sasa.Operators<T>.Multiply
The Sasa.Operators<T>.Multiply operation takes two T arguments, and returns their multiplication:
var x = Operators<int>.Multiply(9, 2); Console.WriteLine(x); // output: // 18
Sasa.Operators<T>.Negate
The Sasa.Operators<T>.Negate operation takes a T argument, and returns its negation:
var x = Operators<int>.Negate(9); Console.WriteLine(x); // output: // -9
Sasa.Operators<T>.Not
The Sasa.Operators<T>.Not operation takes a T argument, and returns logical/bitwise not of the value:
// given twos-complement, not(-1) == 0 var x = Operators<int>.Not(-1); Console.WriteLine(x); // output: // 0
Sasa.Operators<T>.NotEqual
The Sasa.Operators<T>.NotEqual operation takes two T arguments and returns a bool argument corresponding to their inequality:
var x = Operators<int>.NotEqual(6, 2); Console.WriteLine(x); // output: // true
Sasa.Operators<T>.Or
The Sasa.Operators<T>.Or operation takes two T arguments, and returns their logical/bitwise-or:
var x = Operators<int>.Or(1, 2); Console.WriteLine(x); // output: // 3
Sasa.Operators<T>.Plus
The Sasa.Operators<T>.Plus operation takes a T argument and returns their logical/bitwise-plus of the argument:
var x = Operators<int>.Plus(-1); Console.WriteLine(x); // output: // -1
Sasa.Operators<T>.RightShift
The Sasa.Operators<T>.RightShift operation takes a T argument, followed by an Int32 argument, and returns a T "right-shifted" by the Int32:
var x = Operators<int>.RightShift(8, 3); Console.WriteLine(x); // output: // 1
Sasa.Operators<T>.Subtract
The Sasa.Operators<T>.Subtract operation takes two T arguments, and returns their subtraction:
var x = Operators<int>.Subtract(1, 3); Console.WriteLine(x); // output: // -2
Sasa.Operators<T>.True
The Sasa.Operators<T>.True operation takes a T argument and returns a bool based on whether the argument corresponds to that T's true value:
Console.WriteLine(Operators<bool>.True(true)); Console.WriteLine(Operators<bool>.True(false)); // output: // true // false
Sasa.Operators<T>.Xor
The Sasa.Operators<T>.Xor operation takes two T arguments, and returns their logical/bitwise exclusive-or:
var x = Operators<int>.Xor(1, 3); Console.WriteLine(x); // output: // 2
Sasa.Operators<T0, T1>
Not all operators are defined over only a single type, some are defined over two types. Sasa.Operators<T0, T1> provides efficient access to such operators, like explicit or implicit casting from a type T0 to T1.
Sasa.Operators<T0, T1>.Decrement
The Sasa.Operators<T0, T1>.Decrement operation takes a T0 argument, and returns a "decremented" T1 argument:
var x = Operators<double, double>.Decrement(1.5); Console.WriteLine(x); // output: // 0.5
Sasa.Operators<T0, T1>.Equal
The Sasa.Operators<T0, T1>.Equal operation takes a T0 and T1 and returns a bool argument corresponding to their equality:
var x = Operators<int, int>.Equal(6, 2); Console.WriteLine(x); // output: // false
Sasa.Operators<T0, T1>.Explicit
The Sasa.Operators<T0, T1>.Explicit operation takes a T0 and returns a T1 corresponding to an explicit cast from T0 to T1:
var x = Operators<int, float>.Explicit(6); Console.WriteLine(x); // output: // 6.0
Sasa.Operators<T0, T1>.GreaterThan
The Sasa.Operators<T0, T1>.GreaterThan operation takes a T0 and T1 and returns a bool based on whether the first argument is greater than the second:
var x = Operators<int, float>.GreaterThan(1, 2F); Console.WriteLine(x); // output: // false
Sasa.Operators<T>.GreaterThanOrEqual
The Sasa.Operators<T0, T1>.GreaterThanOrEqual operation takes a T0 and T1 and returns a bool based on whether the first argument is greater than or equal to the second:
var x = Operators<int, float>.GreaterThanOrEqual(1, 2F); Console.WriteLine(x); // output: // false
Sasa.Operators<T0, T1>.Implicit
The Sasa.Operators<T0, T1>.Implicit operation takes a T0 and returns a T1 corresponding to an implicit cast from T0 to T1:
var x = Operators<int, double>.Implicit(6); Console.WriteLine(x); // output: // 6.0
Sasa.Operators<T0, T1>.Increment
The Sasa.Operators<T0, T1>.Increment operation a T0 argument and returns an "incremented" T1:
var x = Operators<int>.Increment(98); Console.WriteLine(x); // output: // 99
Sasa.Operators<T0, T1>.LeftShift
The Sasa.Operators<T0, T1>.LeftShift operation a T0 argument, followed by an Int32 argument, and returns a T1 "left-shifted" by the Int32:
var x = Operators<int, int>.LeftShift(1, 3); Console.WriteLine(x); // output: // 8
Sasa.Operators<T0, T1>.Negate
The Sasa.Operators<T0, T1>.Negate operation takes a T0 argument, and returns its negation as type T1:
var x = Operators<int, int>.Negate(9); Console.WriteLine(x); // output: // -9
Sasa.Operators<T0, T1>.Not
The Sasa.Operators<T0, T1>.Not operation takes a T0 argument, and returns logical/bitwise not of the value as type T1:
// given twos-complement, not(-1) == 0 var x = Operators<int, int>.Not(-1); Console.WriteLine(x); // output: // 0
Sasa.Operators<T0, T1>.NotEqual
The Sasa.Operators<T0, T1>.NotEqual operation takes a T0 and T1 and returns a bool argument corresponding to their inequality:
var x = Operators<int, int>.NotEqual(6, 2); Console.WriteLine(x); // output: // true
Sasa.Operators<T>.Or
The Sasa.Operators<T>.Or operation takes two T arguments, and returns their logical/bitwise-or:
var x = Operators<int>.Or(1, 2); Console.WriteLine(x); // output: // 3
Sasa.Operators<T0, T1>.RightShift
The Sasa.Operators<T0, T1>.RightShift operation takes a T argument, followed by an Int32 argument, and returns a T "right-shifted" by the Int32:
var x = Operators<int>.RightShift(8, 3); Console.WriteLine(x); // output: // 1
Sasa.Operators<T0, T1, T2>
Sasa.Operators<T0, T1, T2> is the granddaddy of the operators classes, as it exposes the full generality of binary operators over 3 possible type parameters. In other words, it's possible to define an addition operator that takes a T0 and a T1 and returns a T2. Unary operators can only have two possible type parameters, so they are only available via Sasa.Operators< T0, T1>.
Sasa.Operators<T0, T1, T2>.Add
The addition operator Sasa.Operators<T0, T1, T2>.Add, takes arguments T0 and T1 and returns their addition as type T2:
var x = Operators<int, long, double>.Add(1, 2); Console.WriteLine(x); // output: // 3.0
Sasa.Operators<T0, T1, T2>.And
The Sasa.Operators<T0, T1, T2>.And takes arguments T0 and T1 and returns their bitwise/logical 'and' as type T2:
var x = Operators<int, uint, uint>.And(1, 3); Console.WriteLine(x); // output: // 1
Sasa.Operators<T0, T1, T2>.Divide
The Sasa.Operators<T0, T1, T2>.Divide operation takes arguments T0 and T1 and returns their division as type T2:
var x = Operators<long, int, int>.Divide(6, 2); Console.WriteLine(x); // output: // 3
Sasa.Operators<T0, T1, T2>.Equal
The Sasa.Operators<T0, T1, T2>.Equal operation takes arguments T0 and T1 and returns their equality as type T2, where T2 must implement the true/false operators:
var x = Operators<int, int, bool>.Equal(6, 2); Console.WriteLine(x); // output: // false
Sasa.Operators<T0, T1, T2>.GreaterThan
The Sasa.Operators<T0, T1, T2>.GreaterThan operation takes arguments T0 and T1 and returns their > as type T2, where T2 must implement the true/false operators:
var x = Operators<int, int, bool>.GreaterThan(1, 2); Console.WriteLine(x); // output: // false
Sasa.Operators<T0, T1, T2>.GreaterThanOrEqual
The Sasa.Operators<T0, T1, T2>.GreaterThanOrEqual operation takes arguments T0 and T1 and returns their >= as type T2, where T2 must implement the true/false operators:
var x = Operators<int, int, bool>.GreaterThanOrEqual(1, 2); Console.WriteLine(x); // output: // false
Sasa.Operators<T0, T1, T2>.LessThan
The Sasa.Operators<T0, T1, T2>.LessThan operation takes arguments T0 and T1 and returns their < as type T2, where T2 must implement the true/false operators:
var x = Operators<int, int, bool>.LessThan(1, 2); Console.WriteLine(x); // output: // true
Sasa.Operators<T0, T1, T2>.LessThanOrEqual
The Sasa.Operators<T0, T1, T2>.GreaterThanOrEqual operation takes arguments T0 and T1 and returns their <= as type T2, where T2 must implement the true/false operators:
var x = Operators<int, int, bool>.LessThanOrEqual(1, 2); Console.WriteLine(x); // output: // true
Sasa.Operators<T0, T1, T2>.Modulo
The Sasa.Operators<T0, T1, T2>.Modulo operation takes arguments T0 and T1 and returns their modulus as type T2:
var x = Operators<int, int, int>.Modulo(9, 2); Console.WriteLine(x); // output: // 1
Sasa.Operators<T0, T1, T2>.Multiply
The Sasa.Operators<T0, T1, T2>.Multiply operation takes arguments T0 and T1 and returns their multiplication as type T2:
var x = Operators<int, int, int>.Multiply(9, 2); Console.WriteLine(x); // output: // 18
Sasa.Operators<T0, T1, T2>.NotEqual
The Sasa.Operators<T0, T1, T2>.NotEqual operation takes arguments T0 and T1 and returns their inequality as type T2, where T2 must implement the true/false operators:
var x = Operators<int, int, bool>.NotEqual(6, 2); Console.WriteLine(x); // output: // true
Sasa.Operators<T0, T1, T2>.Or
The Sasa.Operators<T0, T1, T2>.Or operation takes arguments T0 and T1 and returns their logical/bitwise-or as type T2:
var x = Operators<int, uint, uint>.Or(1, 2); Console.WriteLine(x); // output: // 3
Sasa.Operators<T0, T1, T2>.Subtract
The Sasa.Operators<T0, T1, T2>.Subtract operation takes arguments T0 and T1 and returns their subtraction as type T2:
var x = Operators<int, int, int>.Subtract(1, 3); Console.WriteLine(x); // output: // -2
Sasa.Operators<T0, T1, T2>.Xor
The Sasa.Operators<T0, T1, T2>.Xor operation takes arguments T0 and T1 and returns their logical/bitwise exclusive-or as type T2:
var x = Operators<int, int, uint>.Xor(1, 3); Console.WriteLine(x); // output: // 2
Most of the functions in Sasa that are defined on generic numeric types utilize these classes to avoid code duplication. For instance, Sasa.Numbers covered in a previous post, defines the UpTo extension method like so:
public static IEnumerable<T> UpTo<T>(this T start, T end, T step) where T : IComparable<T> { if (start.CompareTo(end) > 0) throw new ArgumentOutOfRangeException("start", "Must be less than or equal to parameter 'end'."); if (step.CompareTo(default(T)) <= 0) throw new ArgumentOutOfRangeException("step", "Must be greater than 0."); if (Operators<T>.Add == null) throw new ArgumentException("T must provide an addition operator."); for (var i = start; i.CompareTo(end) < 0; i = Operators<T>.Add(i, step)) { yield return i; } }
Comments