« Wellington .NET user group - Windows 8 for .NET developers | Main | Discriminated unions in F# »
January 28, 2012
Action, Func, void and unit
The .NET Framework defines two ‘standard’ delegate types: Action<> and Func<>. The Func types are used for delegates that return values, and the Action types for those that don’t – in C# terms, for void methods.
This distinction can often be very annoying. Consider a helper method such as:
public static T WithSomeResource<T>(Func<T> f) { AcquireSomeResource(); try { return f(); } finally { ReleaseSomeResource(); } }
This works fine when the thing you want to do returns a value:
int y = Helpers.WithSomeResource(() => x + 1);
But the compiler won’t let you pass anything that doesn’t return a value:
Helpers.WithSomeResource(() => Console.WriteLine(x)); // error
You have to create a separate overload that takes an Action instead of a Func, and has an identical implementation except for omitting the return keyword. Granted, that’s not much work for a simple helper like the above, but what if it were more complicated? Or what if you had a dozen of them? Boring! Also, maintenance nightmare!
The reason for this distinction is that although void sits in the position where you expect the return type to sit, and looks like a return type, it isn’t. Visual Basic makes this much clearer by distinguishing between Sub and Function instead of abusing the return type identifier. So in the WithSomeResource generic method, the type parameter T can’t be void, because void isn’t a type. And so we end up needing the separate Action types, which do the job of Func<void>.
Contrast this with F#. F# doesn’t have void: it has unit. This may seem like it’s just a difference in naming, but it isn’t, because unit in F# is a real type. A function that ‘doesn’t return a value’ in F# is considered to return type unit.
let f (s : string) = Console.WriteLine(s);; val f : string –> unit
So in F#, every function has a return type. And therefore F# doesn’t need to distinguish between functions and actions.
let withSomeResource f = acquireSomeResource() try f() finally releaseSomeResource();;
val withSomeResource : (unit –> 'a) –> 'a
The F# version of withSomeResource can be called interchangeably with ‘functions’ (that return real values) and ‘actions’ (that return unit) because unit is a valid type, and so the type parameter 'a can be unit just like any other type.
let y = withSomeResource (fun () –> x + 1);; // okay withSomeResource(fun () –> Console.WriteLine(x));; // also okay
Having unit instead of void makes it easier to write generic functions, because you don’t need to write separate implementations for the ‘value’ and ‘no value’ cases.
And that, my liege, is how we know the earth to be banana shaped.
January 28, 2012 in Software | Permalink
TrackBack
TrackBack URL for this entry:
https://www.typepad.com/services/trackback/6a00d8341c5c9b53ef0168e63658d6970c
Listed below are links to weblogs that reference Action, Func, void and unit:
Comments
I agree 100%, a unit type just makes everything a function which in turn enhances composability. I've been referencing the F# runtime in C# apps for this and many other reasons ( see http://bugsquash.blogspot.com/2011/10/10-reasons-to-use-f-runtime-in-your-c.html ), in FSharpx we have helper functions to convert between Actions and Func[..,Unit]
Posted by: Mauricio Scheffer at Jan 28, 2012 4:31:41 PM
It's the same approach (the Unit use) they have taken in Reactive Extensions, but that is a very declarative approach for c# as well :)
Posted by: Stefan Daugaard Poulsen at Jan 30, 2012 11:09:32 PM
Even more frustrating is that Predicate (of T) is not interchangeable with Func (of T, bool ) . The latter is used with the Linq IEnumerable extensions. More info and discussion here: http://stackoverflow.com/questions/665494/c-why-funct-bool-instead-of-predicatet
I'm not sure why the runtime can't unify these types...
Posted by: govert at Jan 30, 2012 11:27:02 PM