4 ms·
I'd argue that that's painting with too broad a brush. C# has picked up a lot of features to support functional programming, and has made great strides in reduc
by bunderbunder 8y ago
I'd argue that that's painting with too broad a brush. C# has picked up a lot of features to support functional programming, and has made great strides in reducing boilerplate and clutter, but there are still all sorts of things that F# has and C# either lacks, or requires a lot more verbosity to accomplish the same thing.
Pattern matching, for starters. It's not just chained if-statements on steroids; F#'s decision to make partial matching emit a compiler error is a real sea change in terms of the level of compiler support you get in guarding against unintended consequences in an evolving codebase.
Type providers. They let you interact with external data sources with about the level of ceremony you get in a dynamic language, but are still statically typed. The SQL type provider is particularly impressive. Not only does it generate your DTOs for you without ceremony, even when you `SELECT *`, even when the schema changes, but, if you make a mistake in an inline SQL query, you'll get red squigglies under exactly the spot in the string where the error is, just like if it had been a syntax error in the F# or C# code.
Computation expressions. F#'s equivalent of Haskell's do notation. C# accomplishes similar levels of purpose-specific expressivity in some specific areas - LINQ, async/await, etc - by adding syntactic sugar to the compiler, which means only the C# compiler team gets to do it. Computation expressions let you roll your own.
Currying and partial application. You can achieve the same result in C# with higher order programming, but it gets messy quickly. Also, there's an argument to be made that, similar to how objects are heavyweight closures, dependency injection is heavyweight currying.
- mattferderer 8y agoDo you have thoughts on C# 8's increased pattern matching features?
- bunderbunder 8y agoI haven't actually used C#, or any .NET language, since before 7 came out. Based on what I've seen as a sideline observer, though, it looks to me like C#'s pattern matching and destructuring are just syntacitc sugar, and therefore still significantly weaker than what F# has. I don't think you can get to the level of type safety that F#'s pattern matching provides without F#-style algebraic data types. The reason is that a set of algebraic types in F# is effectively bundle of types that are collectively sealed. In C#, you can really only seal the terminal classes in a cluster of types, which means that anyone else can come along and add a new class to the type at a later date, after the initial module is compiled, possibly even at run time. That undercuts your ability to ensure that a branching statement is airtight. I believe C# also still lacks active patterns, which are a pretty killer feature if you're looking to do data-oriented programming.
- victorNicollet 8y agoC# now has active patterns (which feel like the `is` pattern matching) in the ability to declare a variable as you pass it as an out parameter to an extension function, e.g. if (num.IsNotPrime(out var a, out var b)) WriteLine($"{num} = {a} × {b}");
- platz 8y agoany kind of pattern matching without the ability to check for exhaustiveness defeats the safety they provide, which is need to make that comparable in utility and power to inheritance (i.e. so you can actually replace inheritance w/ pattern matching.) Much like how the compiler assists you when writing subclasseses so that you've implemented all the required methods, real Pattern Matching (F#) gives you the same guarantees from the compiler that you've handled all the possible cases appropriately. Exhaustiveness checking is what elevates pattern matching to the other side of the expression problem, which is about getting feedback from the compiler to help you write programs. a glorified switch statement does not help you do that, and so you still have to write subclasses to get compiler feedback.
- bonesss 8y ago> pattern matching without the ability to check for exhaustiveness defeats the safety they provide This holds for a lot of C# vs F# issues... There is a fundamental difference between "it's valid, but our tooling hides it from us" and "the execution environment guarantees that it can never happen". The single pass compiler, functional foundation, currying syntax, and other features that provide guarantees simply can't be replaced with equally robust syntactical sugar. Much in the same way that writing imperative programs that follow OOP principles in possible, but will never give you the system level guarantees that a compiled class sytem will.
- kod 8y agoWhy is the compiler being single pass related to correctness?
- bunderbunder 8y agoI'm guessing that's a reference to how, in an F# project, files have a specific order in which they're compiled, and you can only refer to things that are declared previously in the current file, or in a previous file. It makes accidentally creating circular dependency graphs, which can be a source of technical debt, difficult to do. There is still a facility for introducing mutual recursion, but you have to do it explicitly.
- victorNicollet 8y agoTo this list, I would add code quotations. You can <@...@> around any expression and get a syntax tree at runtime that you can explore, transform and even compile. While C# does have a similar feature in Linq.Expressions there are entire categories of (completely banal) C# expressions that cannot be quoted.