Hacker News new | ask | show | jobs
by MichaelGG 4325 days ago
F# will almost definitively help any C# code. Even if you use F# as a "better C#" you'll see improvements. Just removing excessive type annotations is a nice step up in clarity.

F#'s benefit will come as a bunch of tiny improvements, "programming in the small" as they call it. For instance:

  let xs = [ while r.Read() do yield r.GetInt 0 ]
In C#, it's:

  var xs = new List<int>();
  while (r.Read()) { xs.Add(r.GetInt(0)); }
Or:

  let f x = 
    let x = try int s with _ -> -1
    x * 3
In C#, it's uglier. First because try... isn't an expression. Second, because you cannot shadow variables by rebinding them, so you always have to keep "old" vars around and in scope, and cannot reuse handy variable names:

  int f(string x) {  
   int x1;
   try { x1 = int.Parse(s); }
   catch { x1 = -1; }
   return x1 * 3;
  }

Or:

  let x = 
    use db = new DB()
    db.GetX()
In C#, you've got to declare x outside a block:

  SomeType x;
  using(var db = new DB()) {
    x = db.GetX()
  }
Which is more annoying than it might seem. How much nicer is it to be able to create a new scope at any point in a function, and return a value out of it cleanly?

These are by no means a complete or even important showcase of C#'s lacking. Just a few quick thoughts off the top of my head. In general, every time I'm writing C# code, I keep realising how things would be much more concise if I was in F#.

1 comments

F# is a nice language, but not a very necessary one (especially given the lack of tooling).

1) Simply write a quick extension method (not ideal but not a reason to switch languages):

    var xs = r.YourExtensionMethod<int>(rr => rr.GetInt(0)).ToList();
2) Simply use the appropriate built-in method:

    int f(string x)
    {
        int number; /*Will be not necessary in C#6.*/
        return Int32.TryParse(value, out number) ? numeber * 3 : -1;
    }

3) It's a matter of taste. Not a bad feature, but not a killer one either.
None of them are killer features. Nor is pattern matching, assorted comprehensions, array slicing, binary literals, bytestrings, tuples, active patterns, records, type inference, immutability, shadowing, nested functions, custom operators, typechecked printf, type providers, workflows[1], sum types, agents, etc. etc.

But it sure all adds up.

Switching for a codebase might not be a wise move for many reasons. But writing new code doesn't have those excuses.

Actually workflows are the closest to a "killer" feature but C# took the most popular, async, and hard-coded it in.

All those words surely do sound impressive when you list them all together just like that. But you can remove at least pattern matching and records, as they are coming to C#. At least some of them is a matter of taste (e.g. nested functions , global inference and custom operators - the latter two can reduce readability IMHO) etc. For me however the killer feature of C# is ReSharper and other tooling. There is no point to argue, F# is a great language, but C# is a great one as well, no need to write code samples to misuse the language and make it look bad. I considered using F# for physics computations (to leverage the units of measure) but unfortunately it provides no solution for marking external types with them (which means I cannot integrate it with e.g. MonoGame). I still have a hope for finding a good use case for F#.
Pattern matching in C# would be a rather big departure and change. And then to match F#'s level of deconstruction, that'd be neat. C# can become F# if it wants, they just have to add in features. They've been hesitant to do so based on limitations of the compiler codebase, as well as fears that C# might get too difficult for mediocre programmers.

I'm not sure how new List<string> { "a", "b" } is more readable than new List<_> {"a", "b"), but hey, sure, if you want to argue type inference is bad, go for it. F# also lacks loop constructs like break/continue.

I don't feel I've misrepresented C# at all to make it look bad. I've written a lot of C# code and a fair amount of F# code. Line by line, char-by-char, expression-by-expression, F# is simply much less code. Those examples are just things off the top of my head, from real codebases.

C#'s alright, because the competition (like Java) is laughable. So in that sense, it's "great". In absolute terms, it doesn't measure up (and this isn't a secret, bit-by-bit C# adopts features F# proved out.)

Units-of-measurements are implemented via erasure, yes. How would you represent float<m> externally to make it available to common types, but without losing performance? The compiler consuming them would need to be aware of it. And at runtime, you certainly don't want extra overhead, and I don't think the CLR has any efficient way of exposing primitives with additional type info. It's an unfortunate tradeoff.