-
Notifications
You must be signed in to change notification settings - Fork 15
TypedLambdas
The C# compiler's ability to imply types is ... a little odd. The rules around it are complex, and - to be honest - I'd hesitate to claim I fully understand them. However, they can be summed up reasonably accurately as:
- For a variable declaration, if the type of expression after the
=
is fully deducible, it will imply the type of the variable. - For a lambda, the types of the parameters is never implied from their use in the lambda body. However, the return value will be implied from the body if possible.
- If the lambda is supplied as a parameter to a generic method, then, as long as that method's generic parameters are explicitly stated, then the compiler will infer the types of the lambda's parameters.
- Conversely, if the types of the parameters of the lambda are explicitly stated, then the return type, and the method's generic parameter types can be inferred.
- For method groups (and this includes "groups" with only one method), the compiler downright refuses to infer anything. If supplied as a parameter to a generic method, then that method's generic parameters' types must be explicitly declared. However, when explicitly declared like this, the compiler will select the correct method from the method group to assign to the method.
Points 3 - 5 can be used to take lambdas and method groups and transform them into a fully deducible value, allowing var
to be used. This is demonstrated with the following code:
public static Func<T, T, T> Lambda<T>(Func<T, T, T> f) => f;
public static double Sum(double x, double y) => x + y;
public void Foo()
{
var multiply = Lambda<double>((x, y) => x * y);
var sum = Lambda<double>(Sum);
var result1 = multiply(2, 3); // result == 6
var result2 = sum(2, 3); // result == 5
}
By passing both the lambda and method (group) through the Lambda
method, they are transformed into fully-typed Func<double, double, double>
types and so var
can be used to declare a variable that holds these function values.
Succinc<T> offers a range of such methods to allow lambdas and method (bodies) to be typed in this way.
The Lambda
methods can be used to type functions that take 1 - 10 parameters of one type and that have the same return type. Because there is only one type involved, each overload only has one type parameter, avoiding the need to explicitly type the parameters. So the following three lines of code are equivalent:
Func<int, int, int, int, int, int> x = (a, b, c, d, e) => a + b + c + d + e;
var y = Lambda((int a, int b, int c, int d, int e) => a + b + c + d + e);
var z = Lambda<int>((a, b, c, d, e) => a + b + c + d + e);
The Transform
methods can be used to type functions that take 1 - 10 parameters of one type, but that return a different type, ie they transform the inputs into a output of a new type. Because there are two types involved, we can take advantage of the compiler's ability to infer the return type when only using one or two parameters. When many parameters are involved though, the type parameters can be supplied to simplify the expression:
var x = Transform((int a) => a == 0 ? Maybe<int>.None() : Maybe<int>.Some(a));
var y = Transform<int, string>((a, b, c, d, e) => $"{a}-{b}-{c}-{d}-{e}");
The Func
methods can be used to type functions that take 1 - 10 parameters of more than one type. Just like with Transform
we can either specify the types of the parameters via the generic type parameters or the lambda's parameters, but because the return type can be inferred, it's normally shorter to use the latter approach:
var x = Func((int a, double b) => a < 0 ? b * -1 : b);
var y = <int, double, double>((a, b) => a < 0 ? b * -1 : b));
The Lambda
methods can be used to type actions that take 1 - 10 parameters of one type. Because there is only one type involved, each overload only has one type parameter, avoiding the need to explicitly type the parameters. So the following three lines of code are equivalent:
Action<int, int, int, int, int, int> x = (a, b, c, d, e) =>
{ WriteLine($"{a + b + c + d + e}"; };
var y = Lambda((int a, int b, int c, int d, int e) =>
{ WriteLine($"{a + b + c + d + e}"; });
var z = Lambda<int>((a, b, c, d, e) => { WriteLine($"{a + b + c + d + e}"; });
Note that {}
is used for the lambda bodies in the above example. Succinc<T> uses the name Lambda
for methods that type both Func
lambdas and Action
lambdas. This seems to cause the compiler problems at times though in that favours treating a lambda without a return as a badly behaved Func
, rather than an Action
. Using {}
switches this to it favouring Action
and so y
and z
end up typed correctly.
The Action
methods can be used to type actions that take 1 - 10 parameters of more than one type. Just like with Transform
and Func
we can either specify the types of the parameters via the generic type parameters or the lambda's parameters. Because there is no return type for actions, it's very much a case of personal preference as to which approach to take:
var x = Action((int a, double b) => WriteLine($"{a < 0 ? b * -1 : b}"));
var y = Action<int, double>((a, b) => WriteLine($"{a < 0 ? b * -1 : b}"));
Action
/Func
conversionsCycle
methods- Converting between
Action
andFunc
- Extension methods for existing types that use
Option<T>
- Indexed enumerations
IEnumerable<T>
cons- Option-based parsers
- Partial function applications
- Pattern matching
- Pipe Operators
- Typed lambdas
Any
Either<TLeft,TRight>
None
Option<T>
Success<T>
Union<T1,T2>
Union<T1,T2,T3>
Union<T1,T2,T3,T4>
Unit
ValueOrError