I've been looking at F# recently, and while I'm not likely to leap the fence any time soon, it definitely highlights some areas where C# (or library support) could make life easier.
In particular, I'm thinking about the pattern matching capability of F#, which allows a very rich syntax – much more expressive than the current switch/conditional C# equivalents. I won't try to give a direct example (my F# isn't up to it), but in short it allows:
- match by type (with full-coverage checking for discriminated unions) [note this also infers the type for the bound variable, giving member access etc]
- match by predicate
- combinations of the above (and possibly some other scenarios I'm not aware of)
While it would be lovely for C# to eventually borrow [ahem] some of this richness, in the interim I've been looking at what can be done at runtime – for example, it is fairly easy to knock together some objects to allow:
var getRentPrice = new Switch<Vehicle, int>()
.Case<Motorcycle>(bike => 100 + bike.Cylinders * 10) // "bike" here is typed as Motorcycle
.Case<Bicycle>(30) // returns a constant
.Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
.Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
.ElseThrow(); // or could use a Default(...) terminator
where getRentPrice is a Func<Vehicle,int>.
[note – maybe Switch/Case here is the wrong terms… but it shows the idea]
To me, this is a lot clearer than the equivalent using repeated if/else, or a composite ternary conditional (which gets very messy for non-trivial expressions – brackets galore). It also avoids a lot of casting, and allows for simple extension (either directly or via extension methods) to more-specific matches, for example an InRange(…) match comparable to the VB Select…Case "x To y" usage.
I'm just trying to gauge if people think there is much benefit from constructs like the above (in the absence of language support)?
Note additionally that I've been playing with 3 variants of the above:
- a Func<TSource,TValue> version for evaluation – comparable to composite ternary conditional statements
- an Action<TSource> version – comparable to if/else if/else if/else if/else
- an Expression<Func<TSource,TValue>> version – as the first, but usable by arbitrary LINQ providers
Additionally, using the Expression-based version enables Expression-tree re-writing, essentially inlining all the branches into a single composite conditional Expression, rather than using repeated invocation. I haven't checked recently, but in some early Entity Framework builds I seem to recall this being necessary, as it didn't like InvocationExpression very much. It also allows more efficient usage with LINQ-to-Objects, since it avoids repeated delegate invocations – tests show a match like the above (using the Expression form) performing at the same speed [marginally quicker, in fact] compared to the equivalent C# composite conditional statement. For completeness, the Func<…> based-version took 4 times as long as the C# conditional statement, but is still very quick and is unlikely to be a major bottleneck in most use-cases.
I welcome any thoughts / input / critique / etc on the above (or on the possibilities of richer C# language support… here's hoping ;-p).
Best Answer
After trying to do such "functional" things in C# (and even attempting a book on it), I've come to the conclusion that no, with a few exceptions, such things don't help too much.
The main reason is that languages such as F# get a lot of their power from truly supporting these features. Not "you can do it", but "it's simple, it's clear, it's expected".
For instance, in pattern matching, you get the compiler telling you if there's an incomplete match or when another match will never be hit. This is less useful with open ended types, but when matching a discriminated union or tuples, it's very nifty. In F#, you expect people to pattern match, and it instantly makes sense.
The "problem" is that once you start using some functional concepts, it's natural to want to continue. However, leveraging tuples, functions, partial method application and currying, pattern matching, nested functions, generics, monad support, etc. in C# gets very ugly, very quickly. It's fun, and some very smart people have done some very cool things in C#, but actually using it feels heavy.
What I have ended up using often (across-projects) in C#:
** But do note: The lack of automatic generalization and type inference really hinder the use of even these features. **
All this said, as someone else mentioned, on a small team, for a specific purpose, yes, perhaps they can help if you're stuck with C#. But in my experience, they usually felt like more hassle than they were worth - YMMV.
Some other links: