Hacker News new | ask | show | jobs
by neonsunset 549 days ago
Great projects <3

I agree, source-generation is particularly unfortunate. I don't think it's a deal-breaker per se however - most of the time a small bindings project in C# is more than enough - I just accept it as a matter of life and get on with it, luckily it has not been particularly troublesome. Perhaps it's a bigger issue for people who would like to keep their projects C#-free (which is unfortunate, because C# is an awesome and productive language too).

The bigger issues for me personally is support for low-level primitives. Because F# has full HM type inference and of course gets to enjoy the same performance of struct generics as C# does - it is tempting to write HPC code for which C# can't infer the full generic signature the way F# and Rust can.

Unfortunately, however, in this specific area F# is outmatched because it has surprisingly less flexible pattern matching - list and array patterns in F# are bound to lists and arrays and you can't as easily match opaque objects, abstract classes or interfaces to a specific type with specific state inside, something that C# lets you do easily, where you can match, deconstruct, inline slice and unbox on any type that simply has the necessary "shape" without being bound to specific core types or interface implementations. You can work around most limitations with active patterns, but there is nothing that you can do that would give you `span is [var x, ..var rest]`.

Continuing the last point - C# has really pushed forward in making span-based and general performance-oriented code simple and idiomatic. In F#, the use of ref structs is much more restricted (please do not hold it against the team that works on it - it's a complex piece of functionality, just look at https://em-tg.github.io/csborrow/ as a start). As a result, F# is an extremely powerful and productive language that sits in an awkward spot where it is frustratingly close to being "the OCamlish Rust for .NET" thanks to its incredible type system, but at the same time if you do care about maximum performance with heavy data manipulation - you will be better served by C# still simply because it would take a lot of effort to make these specific areas in F# work just as well.

Important - these issues are _luxury_. Java ecosystem simply does not provide any alternatives due to limitations of the type system offered by JVM. So while indeed things could have been better, they do not have a proper alternative in Kotlin/Clojure/Scala at all, disqualifying them from certain performance domains completely.

On AOT - as long as you avoid printfn in F# and use string interpolation with Console.WriteLine and such you should be getting no AOT warnings. With library code it can be tricky, but it's usually caused by the abuse of un-analyzable reflection. You can always ping authors to perhaps take a look at it or use something better if available :) I'm enjoying AOC with F# and FSharpPacker too, but sadly don't have time to continue.

2 comments

Agreed on c# having better low-level primitives available. I know Java has some long-term projects in flight (Valhalla and friends), but dotnet has all of that available today.

I’m curious in what industry you’re utilizing those features. I imagine you must be using mostly your own code since much of nuget contains things that don’t take performance as a first priority. I imagine it would be similar to the trading companies using Java where they toss out most of the standard library and most things on maven and create their own faster versions (which is also not dissimilar to gamedevs tossing out STL and making their own low-allocation libraries).

> I’m curious in what industry you’re utilizing those features.

Performance engineering for CDN systems!

> I imagine you must be using mostly your own code since much of nuget contains things that don’t take performance as a first priority.

This was my assumption too. While "much of nuget" trend continues to hold, there are actually quite a few performance-oritned libraries nowadays.

Bindings and binging generators (that are near-zero-cost), managed allocator implementations, high-performance parsers and serializers (which completely wipe the floor with alternatives written in flimsy languages like Go), higher-level APIs for graphics libraries (DX12, Vulkan, ...), high-performance primitive libraries, game engines, and more.

You are correct to point out that performance-sensitive code tends to avoid standard library containers and certain APIs, but CoreLib itself has been exposing quite a few APIs (like ArrayPool) and tends to practically always offer Span<T>-based method overloads so the data can reside in any memory if it needs so (stackalloc, array pool, unmanaged, or anything in between).

Sure, if you have a hot path you will probably avoid using List<T> and StringBuilder in favor of something less allocation-heavy, but it's generally the Unity land that is subjected to pain of fighting with every allocation rather than .NET itself.

While true the type system is a problem on the JVM world, that is taken care by the plethora of choice of implementations, some do include extensions.

Also while .NET is great and I favour it over Java when having the option, it is a guest on mobile OSes constrained by platforms FFI API surface, or subpar APIs (Catalyst on macOS), and Meadow isn't at the same level as PTC, Aicas, microEJ and many other embedded implementations.