Functional C# Revisited - Into the Great Void
Into the Great Void
I conversed with Jimmy Bogard last week regarding some limitations I saw in the C# language and how F# is better suited to handling this issue. One thing that has frustrated me is the fact that the System.Void structure is not treated as a first class citizen, in that I cannot do the following:
Func<int, int, Void> equals = (x, y) => Assert.Equal(x, y);
I get the following error if I even try to do this:
System.Void cannot be used from C# -- use typeof(void) to get the void type object
And why is that exactly? The ECMA Standard 335, Partition II, Section 9.4 "Instantiating generic types" states:
The following kinds of type cannot be used as arguments in instantiations (of generic types or methods):
- Byref types (e.g., System.Generic.Collection.List`1<string&> is invalid)
- Value types that contain fields that can point into the CIL evaluation stack (e.g.,List<System.RuntimeArgumentHandle>)
- void (e.g., List<System.Void> is invalid)
let should f actual = f actual
let not_throw_exception actual =
Assert.DoesNotThrow(Assert.ThrowsDelegate(actual))
which compiles down to:
public static U should<T, U>(FastFunc<T, U> f, T actual)
public static void not_throw_exception(FastFunc<Unit, Unit> actual)
What this allows me to do is pass in a function that will return nothing, and yet, if I passed in a FastFunc<int, int> that would work too with the should function. Both of them are treated the same, yet not in C#. Instead, we are forced to differentiate
Func versus Action?
As we're forced to differentiate our functions, it makes it hard to generalize functional programming in C#. I feel at this point, C# can't be a first class functional language because we need to make this distinction. If we don't return a value, we must use the Action<T> whereas if we do return a value, we must use Func<TArg, TResult>. Let's look at some samples where we have to differentiate.
Let's first look at the forward operator function in C#. This is one we looked at last time:
F# example
let (|>) x f = f x
[1..10] |> List.map(fun x -> x * x)
C# version
public static TResult ForwardFunc<TArg1, TResult>(this TArg1 arg1, Func<TArg1, TResult> func)
{
return func(arg1);
}
- And -
public static TResult ForwardFunc<TArg1, TArg2, TResult>(this TArg1 arg1, Func<TArg1, TArg2, TResult> func, TArg2 arg2)
{
return func(arg1, arg2);
}
This allows me to do the following code:
var mapResult = Enumerable.Range(1, 10).ForwardFunc(x => x.Map(i => i * i));
var mulResult = 3.ForwardFunc((x, y) => x * y, 3);
Whereas if my methods returned void, I'd also have to create functions to match that as well, such as:
public static void ForwardAction<TArg1>(this TArg1 arg1, Action<TArg1> func)
{
func(arg1);
}
- And -
public static void ForwardAction<TArg1, TArg2>(this TArg1 arg1, Action<TArg1, TArg2> func, TArg2 arg2)
{
func(arg1, arg2);
}
And then I can accomplish this code:
true.ForwardAction(x => x.ShouldBeTrue());
Enumerable.Range(1, 10).ForwardAction((x, y) => x.ShouldNotContain(y), 3);
These of course are simplistic examples, and it just shows that you have to think a little bit about whether you return something or not. Not something you have to necessarily think about in F# or other functional languages. Currying is also pretty difficult using the Action delegate as well. It's not really a usable thing at this point. Feel free to correct me, however...
How could you curry an Action given that you curry any normal function such as this?
public static Func<TArg1, Func<TArg2, TResult>> Curry<TArg1, TArg2, TResult>(this Func<TArg1, TArg2, TResult> f)
{
return a1 => a2 => f(a1, a2);
}
Maybe something like this?
public static Action<TArg2> Curry<TArg1, TArg2>(this Action<TArg1, TArg2> func, TArg1 arg1)
{
return a2 => func(arg1, a2);
}
But of course this function can't scale as you add more parameters to this. So, this isn't really an ideal situation. Unless I'm missing something blindingly obvious.
Wrapping It Up
With these given limitations of the void type, lack of type inference on method signatures, etc, it's hard to take C# seriously as a full citizen in the functional programming sense. I think it's a rather large weakness to me. Instead, I think we should focus on languages which already make these semantics easy, such as Haskell, F# and so on for when you need functional programming. Sure, C# can support a lot of functional programming paradigms, but it doesn't quite feel natural.