2019-03-19 12:00:00
Non-idiomatic F#
Mark Seemann wrote a very fine blog post this week entitled The programmer as decision maker, in which he says (among other things) that trying to do functional programming in C# doesn't work out very well. It's a thought-provoking piece, and worth a read.
I want to respond to the point mentioned above, not to argue with it, but to observe that it is asymmetric: Writing non-functional F# works out WAY better than writing functional C#.
Lately I've been writing F# code that a purist would hate. I've been writing F# with roughly the same approach I would use when writing C#.
It turns out this approach works, and has some real benefits. I want to discuss two of them:
if-then-else
I grumble every time I find myself writing C# code like the following:
string s; if (condition1) { s = expr1; } else if (condition2) { s = expr2; } else { s = other; } // do something with s
In C#, if statements are not expressions, so I have to declare the variable separately and then put an assignment in each branch. I also dislike how this code structure takes away the privilege of type inference, forcing me to declare s as string instead of var.
(Yes, I know about the conditional expression operator, but that's not quite the same, especially for cases involving "else if". Basically, there are multiple ways to do this in C#, but all of them are disappointing in some way.)
This works out much nicer in F#:
let s = if condition1 then expr1 elif condition2 then expr2 else other // do something with s
One assignment, with type inference. Elegant. And I like the fact that s is not mutable. Which reminds me...
mutable
One of my favorite things about F# is that it forces me to take extra steps to make a mutable variable.
I suspect that more than half the local variables I use in C# would actually not need to be mutable. Like this one:
var x = obj.Method(); // code that uses x but doesn't modify it
But the language gives me no way to express that, so the compiler cannot enforce it. Suppose I come back later, having forgotten what was going on, and change x. Suppose somebody else comes in later and changes x. I want the compiler to complain.
The F# equivalent is nearly identical:
let x = obj.Method() // code that uses x but doesn't modify it
But x is immutable, and if anybody ever tries to violate that rule, the compiler will fuss.
Note that when I am choosing to be in this mode, writing "non-idiomatic F#", I give myself permission to use mutable without guilt.
let mutable total = 0 for s in a do // do something with s // but also keep a running total of the length total <- total + s.Length let total = total // shadow total <- 5 // error
Yeah, yeah, I like functional folds, but the exercise here is to try writing C#-ish code with F#. So a for loop with mutable variable is a likely way to do this. F# supports this just fine, with the mutable keyword, and with the different syntax for the assignment.
Note that I threw in a little trick. After the loop ends, I added an immutable let binding for total, setting it to the value of "mutable total", thus shadowing the mutable and making it unavailable. So the attempted assignment on the next line will error. In effect, I am celebrating F#'s ability to let me make this variable mutable, but also its ability to specify that the time for mutability has come to an end.
Summary
I'm not claming any of this is the best way to write F#. From a functional programming perspective, these are considered bad practices.
But non-incremental change is hard and scary. You want a really ineffective way to promote F# in the .NET world? Let's tell everybody that they have to retrain all their developers with a completely new paradigm and rewrite all their code (probably twice) with a totally new approach.
I'm a fan of functional programming. I am also a fan of incremental change, and things that have good ROI.
So I find it really cool how easy it is to add a little F# into a C# code base, or to make a gradual, incremental transition from C# to F#. It works, with full interop between the two languages. And adopting F# in this manner starts paying off right away.