F# has been my favorite language since I first encountered it in uni.
F# was/is waaaay out ahead of C# with features like unions, null safety, pattern matching, records, more powerful type inference and generic constraints.
Over the years C# has been implementing these features too, which is good, but they have been implementing them in incompatible ways, which isn't very good. Since the investment in F# has been much smaller than in C#, it hasn't been innovating as fast as C# and has been left behind in some ways.
But it is still a great language and remains broadly compatible with the ecosystem and can deliver equal performance to C# with far less boiler plate
Most incompatibilities can be summarized to "source generators" and tooling that relies on other forms of code generation. This can be fairly easily dealt with by writing a helper C# project with the necessary "glue".
Other than that, is there something specific you have in mind?
F# 9 even supports consuming ref struct generic arguments recently added to C# and plans to introduce their definition in F# itself AFAIK (which is likely going to be a nicer experience than in C#!). It has been doing impressively good job at keeping up thus far and deserves way more recognition for this.
Kind of. It supports the C# Task interface nicely (and has for ages), but it you have a bunch of F# async code then you still have to marry the two somehow, and that's still monstrous.
Or is there a particular case that’s problematic? So far I have not seen any interoperability issues around this (even though yes, async CEs are way less efficient than task CEs) but I’m still learning so additional details would be appreciated.
The two languages have two different async abstractions which at their core aren't compatible in general. When that happens in software, one of several possible options will happen. The thing Microsoft chose here is to give an API that looks like it's compatible but which is subtly broken for a nontrivial fraction of code (other common choices include rejecting any level of compatibility, providing more complicated APIs allowing the user to handle the multi-language nuances, or providing an incomplete API which works as expected).
Much like how when one proposes a perpetual motion machine you know there must be a bug in the math somewhere, when somebody proposes an interop between two incompatible data structures there must also be a drawback. Pick literally any feature of the language other than sequentially executing code (exceptions and threads are usually great ways for languages and specs to fall apart), and you'll likely find places where the interop struggles. A few that come to mind:
- That solution is only one direction. Interoperability needs to go both ways.
- The exception interface between the two constructs is sufficiently poorly defined that I'd call it a footgun, and code that won't wake you up at night usually has to do extra work.
- Performance in that kind of wrapping is a nightmare, both from the overhead of additional runtime layers, and, more importantly, because the last time I checked it was basically "sync over async" in a way that made the type system happy.
- Going back to the exception thing, cancellation tokens are especially poorly handled in that interop one-liner.
- They still haven't fixed blunders like ConfigureAwait(false), and the interop code absolutely has to care about that sort of thing in applicable contexts (the worst offenders being any sort of "main thread owns everything" code like current popular GUI paradigms).
This reads as an AI-generated text that is very emotional but provides few meaningful technical details (no examples, unrelated APIs, ConfigureAwait - really now?).
I'm not sure if this makes me sound more or less artificial, but outside the use of the word "blunder" there were scarcely any strong opinions, much less emotions.
> Few technical details
You came across as having done enough F# and C# to not need a novel to understand. Yes, those APIs are related to the problem of merging tasks and async between the two languages. 95% of the time you won't care, and 5% you'll hit a footgun. Just go write some multilingual GUI async code with a few cancellation tokens and exceptions and report back with the edge cases you find. The two abstractions are sufficiently different that you will necessarily find some, and if you aren't fortunate enough for your F# codebase to be mostly Tasks instead of async the you'll have to actually care.
Okay, sorry. The repeated "monstrous" and focus on how bad it is (which I genuinely disagree with - it's absolutely fine) rubbed me the wrong way.
> and if you aren't fortunate enough for your F# codebase to be mostly Tasks instead of async the you'll have to actually care.
My expectation is that async-based methods are not exposed to consuming from C#. If they are - this is unfortunate. The ideal state is to modernize the implementation and mainly rely on task CEs. Async CE has its advantages in terms implicit cancellation propagation but flowing down a cancellation token explicitly is usually a more pleasant experience than in other languages. Nothing that stands out as monstrous, and if something is - it has to do with the way a particular GUI framework implements it and not the language and platform themselves.
Last note on sync over async - threadpool can deal with it just fine. It has detection mechanism for this which proactively injects threads when a worker thread is expected to be blocked that bypasses regular scaling through hill-climbing.
Which works fine assuming all new development is happening in the F# half of a codebase and you don't have a significant amount of old F# async code lying around.
It doesn't require that. You can do a strangler pattern of sorts and use task for the interop layer, then run each call site through a small helper that converts to F# async.
Or, hell, just throw caution to the wind and text replace. I've heard some folks do that and it worked out well for them + wasn't much time to clean up either. They're especially happy to have better stack traces too.
> There is also a new compile-time error for classes with over 65,520 methods in generated IL. Such classes aren't loadable by the CLR and result in a run-time error. (You won't author that many methods, but there have been cases with generated code.)
The mind boggles.
At any rate, F# is a terrific language. After excel, probably the second best thing Microsoft has ever released. Turns .NET into a reasonable platform.
> After excel, probably the second best thing Microsoft has ever released. Turns .NET into a reasonable platform.
I feel like C# is quite underrated. Very productive language that's relatively easy to teach to anyone that is already familiar with JS or TS. Used in a variety of contexts from game engines to enterprise backends to desktop apps.
A few fumbles by Microsoft early on stunted its growth, IMO, but it's a really good and relatively easy to learn general purpose language.
> A few fumbles by Microsoft early on stunted its growth
Microsoft really, _really_ missed out on a huge opportunity to rebrand .NET into something else when they did the Framework -> Core transition. Despite .NET being cross platform for over ten years and winning benchmarks left and right people still see it a the “slow enterprisey framework that only runs on Windows”.
I've met a handful of folks who used it in the .NET Framework days and hadn't kept up with the changes in Core (multi-platform, object-functional hybrid, minimal syntax for console and APIs, etc.).
You no longer need imports, or namespace, class and main method boilerplate to write a basic C# program; there’s now a way to simply write some lines of code in a file with some implicit imports, and compile/run them like it was more of a scripting language.
It's a beginner friendly and "demo mode" set of features to reduce boilerplate for small programs, such that the minimal "hello world" program is literally 1 line of code.
And a working minimal ASP web app with a http endpoint is not much longer.
C# and .NET was from day 1 designed for "programming in the large" so that large codebases can be sensibly organised.
This however causes some overhead, such that a person coming from Python might have looked at the standard C# "hello world" example with using statements, namespace, class and method wrapping the 1-line payload, and conclude that the language is impossibly clunky and cumbersome. My opinion is the opposite; managing e.g. 50K lines of code without those ways of organising code, is going to be impossibly clunky and cumbersome.
However, it is also true that demo mode is great for 1-file demos that get right to the point.
I think the using statements are fine, even for one-file demos. Python, Go and Node don't have implicit imports like C# and it's fine. I'm actually not entirely sold on the implicitness. Forces the reader to look at the .csproj file and the target framework documentation what is implicitly imported.
Most of the verbosity comes from classes and namespaces. Go and Rust have shown it's possible to design a language for "programming in the large" without classes for everything and with less verbose namespaces.
But to be fair, I'm just getting started with C# so my comment above is likely wrong and biased. Happy to be proven wrong :)
C# has several constructs like anonymous types, tuples, named tuples, and records (for structs and classes). Each has different use cases (and sometimes limited scopes like anonymous types) that can serve different contexts for data modelling.
> with less verbose namespaces
This is, once again, the function of the team and not the language. There is no hard mandate on how namespaces are selected so verbose namespaces is a result of teams preferring it over more concise naming. Part of that might be purely practical. Whereas in JS, you would import a local module by path, C# imports via namespace and convention is to use pathing, but C# will of course allow any namespace convention you like for local modules and does not constrain to strict pathing.
> Most of the verbosity comes from classes and namespaces. Go and Rust have shown it's possible to design a language for "programming in the large" without classes for everything and with less verbose namespaces.
The statement that "C# and .NET was from day 1 designed for programming in the large" is true and factual, supported by design documents that pre-date .NET 1.0, i.e. in the late 1990s
The statement that more-lightweight ways have since been developed of approaching the issue is more subjective. But IMHO it is also largely true, as the programming language world has moved on since then. The C# design has been extended a lot, but is constrained by being backwards-compatible. In some ways it is of its time.
e.g. We might not be "entirely sold on implicit usings" but it was a way to get from existing syntax, to a 1-liner
"hello world". I think that global usings are fine, when used very sparingly. e.g. I am happy for a test project to have a global using Xunit because it will be used so widely in the project code. But not many more global usings.
I very much agree with your comment. C# managed to evolve and adopt many “modern” features while maintaining backward compatibility, which is quite impressive.
Having recently joined a company using C#, and with a background using Go, Python, Node, and similar, I was worried about heavy "enterprise-style" APIs. I was happy to discover the new minimal web API while reading .NET Core documentation.
> Despite .NET being cross platform for over ten years and winning benchmarks left and right people still see it a the “slow enterprisey framework that only runs on Windows”.
Do these people I've never heard of outnumber the people who did upgrade but wouldn't have if it had been branded as a separate product?
It's quite nice now. Tooling is way nicer than it was 10 years ago. My team is all Apple silicon Macs and we deploy to AWS Arm64 instances. Unit tests in GH run on Linux workers.
I was going to ask what the multiplatform deployment story was like, it seems like you've partially answered it - it seems like you ship it with .NET Core as a dependency, kinda like JRE for Java programs?
How heavy of a dependency is it?
And is there a dependency hell situation, if different programs require different versions of .NET Core?
The state of .NET Framework is that it's packaged with Windows so technically it did not require an install - an OS shipped with it.
The state of .NET is something that Java wants to reach in a few years or so. There are basically three ways to deliver an application to the users:
- A single or multiple files containing .NET assemblies and a thin binary launcher (single-file mode controls the exact shipping of this) - this is similar to OpenJDK which requires a runtime to be installed on the system (except OpenJDK does not have the capability to put all assemblies into a single file with a small native section for the launcher).
- A single or multiple files containing .NET assemblies and runtime itself
- A single native executable compiled with NativeAOT
The first two options allow to "merge" assemblies into a single file or ship them separately.
Shipping just .NET assemblies without runtime takes the least amount of space but requires a runtime dependency (it's easy to install on practically any system).
Shipping assemblies + runtime can also be done as a single file where there's a sandwich of native code and .NET assemblies. In order for such executable to not take disproportionate amount of space (65-120+ MiB) it can be trimmed which is recommended - this reduces base binary size down to 10 MiB or less nowadays (and grows as you add dependencies).
And building with NativeAOT relies on the same linker-based trimming but produces a single native statically linked executable. This results in the smallest runtime-independent binaries (they start at about 1-1.2MiB) with the best startup latency but comes with a different performance profile when compared to JIT and is not compatible with features that either rely on JIT or on unbound un-analyzable reflection where ILLink cannot determine the exact scope of code that has to be compiled. The ecosystem has significantly improved since the introduction of this target in .NET 7 to provide users with tools to make their code compatible, if any changes are required at all.
AOT compilation has existed for about 20 years, it only happened to be a commercial product, Excelsior JET, Aonix, PTC, Aicas, are some examples.
What GraalVM, OpenJ9 bring to the table is free beer AOT compilers, and in GraalVM's case, a LLVM like compiler framework that doesn't exist at all in .NET land.
They also have the advantage of using agents to gather the required reflection data, instead of forcing an ecosystem split of having to rewrite existing libraries to make them AOT ready.
There was the Phoenix project from Microsoft Research, which had the goal to replace Visual Studio infrastructure with a CLI based compiler toolchain, but unfortunely that project failed to gain traction within Microsoft.
Shipping runtimes, which .NET Core introduced for .NET, has been a thing in Java land since they exist.
There is also the ability to create single executables, coupled with jlink and jpackage.
Besides OpenJDK offerings, there is a richness of JVM implementations, each to those having other capabilities, like OpenJ9 and Azul with their distributed JIT compiler for example.
.NET is great and I prefer .NET to Java consulting projects when given the option, however it is no accident that Microsoft has decided to become again a Java vendor as well.
For .NET Framework, you are indeed correct, but in practice this was treated largely the same way as e.g. C++ runtime libraries - you just bundled the .NET runtime with your installer.
This has been moot for a long time now, though, since Windows has shipped with some version of .NET preinstalled since Vista.
Its very much a culture thing and not a language thing. C# is very versatile. Go look at Unity code and its a fairly different style. It has its own quirks and there's no such as "the right way" but it doesn't feel like Spring.
Also, extension methods are great. LINQ is amazing and that's partly from extension methods.
It's just sugar to turn functional style into .Syntax(), but because of that the tooling is very nice and things like LINQ can be a fluent API instead of LISP.
You can call it a hack but IMO you get the best of both worlds.
Languages bring along a culture. Sone teams buck the culture of the broader language ecosystem, most do not. IMO, the C# culture is closer to enterprise Java than it is to Typescript, for good or for ill.
These are all fairly mainstream repos so it's not a one off to see "enterprise" style coding in TypeScript.
It's rather the specific use case that determines the style of the code that's written. In the examples above, there's no need for the teams to choose JavaScript classes and inheritance to model the logic, yet it likely better fits the programming model of those modules.
The whole codebase of Nest.js looks an awful lot like Spring or ASP.NET (controller syntax) probably because there's a lot of crossover in terms of what APIs need at scale and how to logically organize that logic.
It just so happens that most use cases for C# and Java favor applications at a larger scale. Another key difference being object scope and lifecycles -- something that rarely comes into play in the generally single-threaded Node runtime. This being one of the key reasons why dependency injection is a thing in C# and Java land.
Both. The language can afford that usage through the design decisions.
I kind of think the best languages would be those that make creating layers of spaghetti indirection nearly impossible. In practice, it's usually much more specious. Names like "CreatorImplementer", "FactoryAbstrator", "DeviceAccessory" and a half dozen equally amorphous things calling each other as if Thomas Pynchon was writing software.
I think I've seen it in all languages I've worked on professionally. Maybe not in Perl.
Agreed. There's plenty of obtuse indecipherable JavaScript these days.
I keep remembering this code I saw in 2014. It was in ember and used mongodb and some bizarre ember features I've long forgotten to stylize things instead of conventional stylesheets.
I had been doing JavaScript since it was called LiveScript in 1995. I knew it very well. This setup was utterly impenetrable. Charging a button color required I think 6 or so different data stores to be manually updated and placed back in alignment because things were validating and cross checking for no good reason other than being a complicated pile of tangled messes. I had to quit the job.
I know how incredible this sounds. I can prove it. He does everything like this. Here you go https://github.com/davideschiera/flame-ui/tree/master ... I'm not quite sure what these 252 files do but I'm pretty sure it could be done in about 25 lines of code in a single function.
I had worked with him previously on a C# codebase almost 20 years ago now. I remember him being just as insane but somehow the language successfully encapsulated the insanity so I never had to deal with it.
There's this fundamental clash. I think things should work, be maintainable, and have the most minimally complex implementation possible - the Ernest Hemingway approach. Some people prefer James Joyce.
Sadly modern idiomatic C# is a bit of a nightmare these days. It's a heavily abstracted/"indirected" style, where you are basically forced to use a debugger to determine where a method call actually goes. The goal seems to be to make things as emergent as possible, with reflection and IL emit determining code flow as much as possible.
I think that F# goes a long way to prevent this, by nature of being functional.
The default C# style has always preferred a large dose of unnecessary runtime flexibility. Sometimes that comes in the form of lots of dynamic dispatch, sometimes it's DI, sometimes it's non-sealed overly large base-class types, and sometimes its some combination thereof - but the net effect is that it's not possible to reason about code by just reading it. Tooling becomes worse and/or less valuable because tooling too cannot reliably trace all possible code paths.
Additionally, Microsoft has an outsized influence on the C# ecosystem. As a result, they wrote quite a few architectural and style guidelines that, naturally, target themselves. It makes sense for a hyper-widely distributed library to consider things like binary compatibility as critical features, but the design rules that follow from that have zero utility for apps, and low utility for most libraries. The C# ecosystem also has an unhealthy veneration for MS provided code. While I'm sure most of the authors are quite competent, there's less experimentation and fewer hidden gems because the ecosystem is tilted towards being followers behind the framework providers path.
As a result, it's much harder to refactor code in that style than code in a less (runtime) flexible style, and it's worse when the libraries heavily used are frameworks that have so many extension points. There's a tradeoff between runtime flexibility and codebase flexibility, and the default .NET C# style heavily leans towards runtime flexibility, which isn't a good tradeoff for most software.
MediatR is part of the problem, yes. Way too much attribute/reflection magic there.
One particularly good example is: attempt to add full support (i.e. query text generation) for PERCENTILE_CONT to EF. That will give a good idea of just how bad the idiomatic C# style can get. Now take the EF style of code/architecture and apply it to an entire product codebase - which is completely a understandable mistake to be made (it's not unreasonable to assume that EF would be a good example of C# code). You know you have a problem when you throw in the towel and have ThingDependencies classes all over the place. The aspnetcore internals don't vary too much from the abomination that is EF.
"Just don't code like that," you might say. The problem with that argument is that pre-existing code exists, people don't code in isolation, and the cargo cult has a ton of momentum behind it.
> your comment about F# being different.
You'll struggle to write that style of code in a functional language. There is such as thing as healthy friction. F# has its own problems, but this is not one of them.
One of the reasons I use Java is for type safety. What's the point when your app compiles OK but then doesn't start because Spring can't satisfy all the dependencies or choose which one. I spend hours trying to get the config right. You end up programming via config.
That’s fair, I consistently underrate it myself. Last time I worked with C# was a big messy codebase written by mostly junior developers who didn’t understand what entity framework was doing behind the scenes. I came away from that experience with the impression that C# is a terrible language.
There’s an interesting problem with “practical” functional languages that we don’t really know what a crappy enterprise F# / OCaml / Haskell codebase looks like - though maybe Haskell is sufficiently popular in crypto these days that there’s some examples. But generally software in these languages is written by highly motivated developers with a lot of flexibility. OTOH a lot of bad C# and Java is written by developers with bad technical management who treats devs as code monkeys. Further a lot of the “technical enablers” of bad C#/Java are bespoke enterprise frameworks that simply don’t exist for functional languages. But in principle, enterprise Haskell could be quite vulnerable to a “productivity-boosting” language extension that locks older Haskell codebases into horrible typeclass golf.
I say all this as someone who loves F# and would only take a C# job if I needed the money. But certainly a lot of the enterprise antipatterns can be clumsily implemented in F#, while a well-written C# codebase is usually pleasant and productive. In particular F# doesn’t totally escape problems like “the Nuget service locator is overengineered for our needs, but we are on a deadline and an excessive abstraction is better than hard-wiring everything.”
My hypothesis is that F# is somewhat self selecting at senior/lead dev's who would have a lot of choice in the language that they get to choose not the army of "code monkey's" as you call them. Generally this would mean higher code quality on average.
The c# team is supposedly putting in serious work on Discriminated Unions. I'm really interested to see how it turns out because f# already does a great job with them.
Discriminated unions is probably the last "must have" feature I've been waiting for in C#. Better pattern matching would be nice to have but honestly not as much of a gamechanger.
The C# team for several reasons decided "rock solid" pattern matching was a higher priority than discriminated unions because good discriminated union code needs good pattern matching. In the last few versions of C# I think pattern matching has gotten really good. List Patterns finally exist now, and that influenced the new iterable collection initializers, which are also pretty good now. Last I checked, the C# team was still debating Dictionary/Hash key lookup Patterns and Dictionary/Key-Value initializers, and that's maybe my last big request in pattern matching other than native discriminated unions, of course.
What is also great about C# is that the language server for editor support uses the real compiler. I once set that up with emacs and it is by far the best experience I had in emacs with any language.
In my experience having mentored devs into F# on very large projects its easier to learn F# if coming from a JS, Python, Go, etc background than C# or Java even without previous .NET exposure. Not due to a skill issue, but more of a psychological one.
IMO to be productive in F# code code tends to be simpler - many patterns that dev's valued learning in a OO context become a little obsolete or less needed as a whole. This is my anecdote but it is the C#/Java/etc crowd that tend to have the most negative immediate reaction to F# - they ask "where is X" and often I immediately say "X is bad, you shouldn't need X - think about why". They associate X with some kind of power/productivity gain, but don't realize there is many ways to do things. Kind of follows the paradigm of "frameworks/patterns are missing language features".
If they keep at it then it eventually clicks but there is almost a psychological "unlearning" that needs to be done which at least in the C# camp creates an almost tribal tension between the two camps - its just too unfamiliar. Whereas for example TS dev's seem to find it somewhat familiar in parts; even people only ever done JS previously I've found have become good F# dev's. Could be because the flow more suits the way you would code in a dynamic language while still being strongly typed (e.g. REPL's, type inference, static modules, functions over classes, etc).
Vouched for this dead comment. If this really should be flagged to death, could I get a quick refresher on what parts are boring reading or off-topic? The comment is an anecdote and not data, but this does not seem like a flamey post, or a low effort post, or a strawman post.
Probably automated. If someone creates a new account then immediately posts big comments, lots of comments, or links, the comments end up dead by default and need vouching. The dead spam comments often found at the bottom of threads are why.
C# and TypeScript are remarkably similar owing to having come from the same designer. But even before then, it seems as if the two languages influenced one another.
I had toyed with it a few years ago, using that lightweight IDE called LINQPad, IIRC, but did not follow up, or keep track of its pros and cons or progress later.
Downsides: lack of jobs, libraries, learning materials, small community, little promotion by Microsoft outside of "what's new in F#" and other announcements.
It's a shame because F# is a beautiful language — it's fun to read, write and maintain. .NET now works well cross-platform with editors other than Visual Studio. General perks of .NET like cross-platform compilation are a nice bonus, for example, on macOS I can:
I'd love to work with F# full-time, but — short of starting my own company using it or convincing Microsoft to pay me to help them showcase its benefits — it's been hard to find work with F#, which drove me to spend time with other languages.
F# can also be compiled with NativeAOT. The main thing this target complains about is printfn and especially printfn "%A" which uses unbound reflection. So as long as you just Console.WriteLine instead it may not emit warnings:
dotnet publish -o {folder} -p:PublishAot=true
Note that -c Release is no longer required - it is now a default for 'publish'. Also, if you are doing a self-contained/non-AOT build, you may want to do it as follows to retain compact binary size:
All the properties above can also be defined within respective .csproj/.fsproj files inside PropertyGroup blocks as e.g. <PublishAot>true</PublishAot> so you don't have to add them to a command every time.
Someone even made a tool for it which builds on top of bflat allowing to compile F# script files into native binaries directly without project setup: https://github.com/ieviev/fflat
Thanks for the tips! I had thought there were still some other gotchas with AOT and F# but it looks like the list is smaller than last time I looked. https://github.com/dotnet/fsharp/issues/13398
Since F# is primarily meant as a functional language, and thus is written as such, learning it through examples when you have no functional language knowledge, there is quite the learning curve.
Compare that to starting to read Python when you come from C#, it reads as English.
You know how seasoned lisp programmers say the parentheses eventually disappear? I felt like F# was the opposite. There were invisible parentheses everywhere and once I figured out where they were, the language made a lot more sense.
But you can see that about anything with an arbitrarily complicated grammar. If you have internalized the grammar, then you can place parentheses into any utterance to show the structure, which means you can see where they would go even if you don't actually write them in.
Yes, the language make a lot more sense if you know how to do that compared to if you don't.
> anything with an arbitrarily complicated grammar
But that’s the thing about F#: the grammar isn’t arbitrarily complicated. It’s really quite simple, which is why mentally inserting lispy parentheses helps me reason about it.
I persoanlly found the syntax weird and the IDE support poor when i was playing with it in 2013
Both of these are surprising to me. It is an indentation language, similar to Python which I love. And it was by Microsoft, which makes Visual Studio which I also loved
Yet I found the syntax a lot odder than Python: multiple ways to call methods (C# style and Ocaml style), a wide selection of strange brackets, `.[i]` for array lookup instead of `[i]` (i think they fixed this eventually). And despite Visual Studio being great for C#, its support for F# wasnt up to scratch
I ended up settling on Scala, which is semantically very similar, but with an easier syntax (not perfect, but better) and decent Intellij suppory (again, nowhere near perfext!)
I feel like going from one niche language to another even more niche language is risky, even if the performance improvement demonstrably true.
I suppose it’s a chicken and egg problem for languages that want to grow their communities (if nobody takes risks on a language it won’t see adoption) but I’m surprised given their problem domain they didn’t go with something more widely known
Not sure what's up with the author but from our brief interactions on X I have gotten an impression that they did not understand performance at a sufficiently low level and implications of using each language (and their compilers!) despite being adamant about the opposite, resulting in technical decisions driven by inaccurate conclusions. It was quite unfortunate and seemed like an emotionally-driven choice.
It's perfectly fine to use an LLVM-based language where it matters, but it's sad when C# could have been used to solve the problem without throwing the baby out with the bathwater.
I've found when I do get the chance to use F# it is in .NET shops, where the tech staff have agency to make decisions. Language deciders, vs language takers. Those places typically just "choose to use it", the places I've been in generally won't advertise for it however.
IMV it thrives in back-end scenarios where correctness, and algorithmic work could occur whilst still needing some speed of development and a large enough ecosystem. It typically doesn't live in large teams hence less jobs but the teams that I've been on that use it have far reach/leverage once they are proficient at it. I've been lucky to be a part of more than one team like this.
Other languages do this too as well (Rust, Go, etc) with different tradeoffs - F#'s advantage to me is if you want easier learning of staff, correctness, interoperability, faster development IME, scripting at times and reasonable performance (e.g. C#/Java/etc class). It's rare you feel you are fighting the language or producing boilerplate or repeated code patterns; at least in the domains I've used it in. At least in my use cases it is the jack of all trades kind of language; that is its problem as well as its strength.
The push back I get is unfamiliarity and training.
Everyone is comfortable with Python. Even if in my mind, F# is great for all the same tasks, it's too big a leap.
We are spoilt with a lot of good tools these days, F# being one. As an ancedote a hybrid F#/Python shop is one use case where I have seen F# thrive. My personal view is that F# is actually better than some other langs for when you want to convert that Python code into something that runs "faster" but still looks somewhat like Python to stakeholders (I personally find F# easier to read than Python for example the generator syntax in Python to me reads backwards). This typically happens at least for me when the business team/data team/quants/actuaries/etc use Python but want you to productionise/scale it. Static typing, and lower level optimisations possible + easier access to multi-threading. I've personally converted from Python to F# models from a math background and seen 100x performance - as a bonus I can talk to the F# code and typically they understand/can read it (even if they couldn't write it directly) vs say C#/Java with a non-whitespace syntax. Allows easier verification.
Well, I made a large, multi-year company/technology bet on top of F# with Phosphor.
After over a year of trying to make it work, we completely rewrote the application in Typescript and Rust. The product we're building is an end-user programming tool where the traditional lines between front-end and back-end work blur, and the .NET ecosystem really didn't lend itself well to this.
Our hope was that we'd be able to use the Fable library to maintain type safety across a variety of technologies while being able to interop and in and out as needed: Fable compiles F# code to JS, Rust, .NET, etc. The reality was that the interop story between the various libraries we'd use was much harder to achieve than initially expected, and managing and updating multiple dependencies and their bindings was an absolute PITA.
The assumption that F# makes for beautiful, efficient code is still a safe one, I think. But the ecosystem and the way that it was designed makes it (in my view) applicable only for applications and software that have very traditional frontend/backend lines. F# would be used for the back-end only in that case.
Today, two of the technologies I'm most excited by (and which we're using internally) are the Effect library, whose Schema library fills in a lot of the gaps in TS's type system, and Moonbit, which is what I imagine a modern version of F# would like, free of the MS/.NET dependencies. Moonbit is really, really well designed (designed by the creator of OCAML's version of Fable, ReScript), and it compiles directly to highly optimized JS, WASM or Native output. We're using Effect in production, and not yet Moonbit, but the promise Moonbit holds as a language built for an AI-first world is pretty fantastic.
I’ve had a good experience exposing F# code that compiles to Typescript modules. The approach was to write the core business logic and validators in F# and the rest of the front-end application in Typescript and the back-end in C#. That is, core business logic and validators in F# and all of the IO in TS and C#.
Is your company related to Darklang by any chance? I recall they were a product similar to yours, written in F# as well, but I haven't kept up with them recently.
Effect is pretty nice, I wish it existed in other languages than TypeScript too. Regarding MoonBit, it seems like its own proprietary programming language so I'd hesitate to move to that over something well known, what are your thoughts on that aspect?
Had a crypto class that let us choose any language that used .NET. My homework in F# was so much easier to read than everyone elses. Would def use it more, but nearly 100% of my data science work is Python.
I really miss working in F#, such a productive language. But I still enjoy keeping tabs on the updates.
I always thought the tooling was quite good, given the size of the community and Microsoft's apparent apathy towards it at times. The biggest pain point for me was accuracy in code test coverage.
I only toyed with F# recently, but coming from Python I really like that I can also use a REPL to experiment stuff: I wonder if experienced F# devs use it too.
I hope to have time to build a small web backend project this winter, to get to know the language and ecosystem better.
I heard good things about Oxpecker for the http part; do you have any recommendation for a good postgresql client/driver ? (I don't like ORMs)
- https://monazita.gitlab.io/monazita/ (i developed it for a project of mine while learning fsharp. Basically works but could be more polished and is pgsql only)
As an polyglot dev who has the chance to use F# dev at times the REPL is how I POC most things - if I'm not sure of an API, even inlined into my actual codebase just hit the "Send to F# interactive" and it runs there and I can try things inside a module. Or when I want to try a new library, or when I want to do a quick benchmark, or as a substitute to a PS script, etc. You can even make F# scripts executable with the shebang `#!/usr/bin/env -S dotnet fsi` which we do a lot for scripts around our projects as an alternative to bash/python in our .NET projects where we already know a dotnet-sdk is installed. Usually runs faster too and typically just as concise. Just my anecdote opinion - the syntax and idioms of F# lean themselves more to REPL programming then C# which typically needs more OO structure rather than small code snippets strung together.
How does the versioning for F# work? These seem like a bunch of nice quality-of-life improvements, but a major version change doesn't seem warranted in terms of semantic versioning - no breaking changes - nor in terms of a major leap in terms of language features that a non-semver project might use as a reason to go version 8 to version 9.
Another comment mentions .NET9 that recently came out, is that the reasoning here, to keep in stride with .NET version numbers?
So .NET and C# have moved recently-ish to yearly single digit incremented releases and I’m guessing F# is doing the same. Not sure if they intentionally made the switch when it would line up with the .NET version or if that’s just a coincidence. C#, for example, released version 13 for .NET 9.
For both C# and F#, the version of .NET indicates the version of the build tooling (msbuild, compilers, nuget packages, etc...) that will be used to build the code whenever you invoke `dotnet build`.
That means, for example, that the F# team _could_ have shipped a language change between versions 8 and 9, but if they didn't then at least they could have shipped a compiler change or an msbuild change that required .NET 9 for whatever reason.
For developers, usually you can just update your code to the latest available runtime version unless you have to wait for your deployment environment to have the latest .NET version installed. Nowadays that's not necessary due to MSBuild changes to create self-contained deployments, but something to know.
A new NET version comes out yearly and the C# and F# versions align with the yearly version but C# is ahead by 4. Semantic versioning is overrated anyway.
It is fun to point out that the VB.NET compiler is still at least 3 versions ahead. As far as I am aware VB.NET hasn't had a major version number bump since 16.0 (around .NET Core 3.x or .NET 5, as I recall), but the VB.NET compiler still also claims the 6 versions before .NET.
C#'s (lucky) 13.0 in .NET 9 is also just partly related to the non-linear version numbering of .NET itself with the long shadow if .NET 4's many minor releases that were also "major", the Core divide, and the eventual "unfork" at 5.0. F#'s 9.0 matching .NET 9 is a fun coincidence, and also just because F# is younger and missed some of the .NET fun like 4.x "minor number is as important as major number" years.
That is indeed one of the best sites for people new to F#, both experienced C# devs and those relatively new to programming. It's rarely updated any more, so you won't find discussion of the new F# 9 features there, but the existing articles are excellent for getting your head wrapped around concepts like `apply` and `bind`.
A great programming language, pity that managment isn't that supportive of it, always feels like they consider it was a mistake adding it to VisualS Studio 2010, given how little of Microsoft own frameworks and Visual Studio tooling have first class support for F#.
So we are mostly left with the community doing most of the work in a company with enought money to burn in deals like the Actvision Blizzard one, but not to staff the F# team appropriately (and they aren't the only one in such a state).
Perhaps it could have been treated better, but then if you compare it with Kotlin, Scala or Clojure - F# ships with the main .NET SDK. It does not need to be installed separately, no user action is required to jump into an F# project immediately or to add an F# project to an existing solution. You can simply dotnet new classlib/console --language F#, dotnet build and it is ready to go.
It could use more marketing, halo projects and word of mouth though.
Kotlin and Scala don't belong to any JVM implementation vendor, like F# belongs to Microsoft.
And if consider Google to be a platform vendor with hands on Kotlin, then Android / Kotlin development experience is certainly much better than F# has ever achieved since its Visual Studio 2010's introduction.
What F# has going for it, despite everything, is its great experience on Windows, versus Haskell and OCaml, which remain UNIX centric at heart.
Should one use the result type for handling errors or exceptions or both? What's the rule in F#?
When should you prefer immutability over mutability since both are possible in F# and it probably has a measurable impact on performance?
When should you use objects instead of records or unions in F#?
Since mutability is possible, any reason to not allow an explicit return or continue? It makes certain code patterns much less nested and easier to read.
louthy/language-ext [1] is a C# library which does its darndest to build a fully functional language inside C#. I found it very interesting and used it for an experimental project (which ended up failing, not sure if my usage of language-ext was partially to blame). People told me I should have just used F#.
The rumors I've heard is that it is the "Oracle Golf Club Handshake Problem" in action: the current C-Suite of Wal-Mart got wined and dined and golf outing-ed a bit too much by Oracle who wanted a large contract expansion with massive revenue boost (for Oracle) and were promised a bunch of software resources/troubleshooting/consultant time in the next contract if they switched to far more Java. That sales job turned Java into a top-down mandate. It is a fun modern version of the "Nobody went wrong ordering more products and consulting from IBM" problem if IBM had an even more ruthless sales department and also owned too much of the database industry and too much of the Payroll/Time & Attendance industry (and worse, their legacy purchasing decisions and sunk costs) because no one has cracked down hard enough on vertical monopolies in the last century.
Allegedly, of course. I'm sure Wal-Mart PR will happily tell you it was their own idea and how Java is the language of the future again, or something to that effect.
Also, whether or not the rumors hold, it is hard to discount the pure Occam's Razor that it was just Wal-Mart's own idea to cut costs because of the simple dumb fact that labor costs in Wal-Mart's home labor markets for Java developers are certainly cheaper than .NET developers in the same job titles/experience levels. A mandate to switch to Java certainly was one cheeky way to do a slow layoff of expensive laborers without doing a real layoff.
While it is fashionable to hate Oracle, they aren't the only Java vendors in town.
As someone that does consulting across multiple programming languages, what I notice is that .NET is too late to the party in the server room wars.
For better or worse, UNIX and ideas derived from it, won the server rooms and cloud deployments, even Azure is mostly Linux nowadays (> 60% as per official numbers).
Due to its previous history, .NET has always had a bad name in UNIX shops (even with Mono), and most companies would rather use something else, unless they are a Microsoft shop, it could be Java, Go, or whatever.
As far as I am aware, Wal-Mart isn't a Microsoft shop and only acquired Jet for the business, not the technology, so like in all acquisitions something like that was bound to happen.
For many years the Wal-Mart purchase of Jet was lauded as a brilliant "acquihire" by Wal-Mart because they had lagged so far behind Amazon technically and Jet gave them the bootstraps to better compete. It has been said that the modern, competitive parts of Wal-Mart's tech platform is largely owed to former Jet engineers and their .NET (especially F#) forward development ideals.
I've never worked for Wal-Mart, I've only seen PR and blog posts and job postings. That is likely its own spin of what happened, too. But during those years Wal-Mart was seen as a stable "Microsoft shop" in terms of what they were hiring for in software developers (if you didn't mind relocating to Arkansas).
Externally, the Java thing did seem to be a "coup" that came out of the blue. Like I said, the easiest explanation is that Wal-Mart, a company famous for ruthless cost cutting and layoffs, felt like they had milked their acquihire of Jet for just long enough to stay competitive and decided the easiest "exit" for those expensive employees was to force a top-down mandated tech stack change to a known cheaper tech stack and gently encourage them to quit on their own initiative in the chaos. (In that Wal-Mart was already very quick to RTO, they had already played that particular card, many other companies are still palming to cause similar layoffs.)
The Oracle rumors are icing on the cake, sure. But also extremely plausible given that is how Oracle's business works today.
I dont know, i have used F# for a while now. My problem with it- is discoverability and reuseability of the created code. It is very hard to read and get in - compared to classic OO and its hard to reuse something created and custom tailored. My 2 cents, may be worth even less, due to my limited experience and a propably wrong approach by me.
It takes a while to shed one's existing OO expectations, but after that, I think you'll find well-organized F# code to be easier to use than C#. Immutability and lack of side-effects makes it much simpler to reason about what someone else's code is doing, and then reuse it.
If you have any specific examples of discoverability/reusability issues in F#, I'd be curious to hear them.
Since the null-related blog post isn't published yet, anyone here knows the reason to implement null rather than write middle-layer in C# for whatever you want to use?
At this point, almost all community and all standard library APIs use nullable reference types (and types that aren't NRTs are obviously expected to always have a value).
This change in F# makes it able to interoperate with C# NRTs, where C#'s T? becomes F#'s T | null and vice versa.
I feel like the ease of integration of F# into existing codebases is one of the major selling points. You don't need to write wrappers in order to consume most existing C# code, which, for example, enables writing a core with business logic in F# while keeping the IO in C# if necessary.
F# has chosen this approach of implementing compatibility with newer C# features such as support for ValueTuple<T> and Span<T>. It has been pretty successful at keeping F# inside the compatibility story.
Kinda disappointed that they allowed null into the language, they should've just made some syntactic sugar or some auto bridge to reinterpret them nullable types as option types.
Well, too bad. Not that I was a huge F# user anyway :D
That would be a breaking change; certainly it's impossible to do that in the presence of reflection, and I imagine there are simpler cases. (See discussion on the similar https://github.com/fsharp/fslang-suggestions/issues/884 .)
F# has been my favorite language since I first encountered it in uni. F# was/is waaaay out ahead of C# with features like unions, null safety, pattern matching, records, more powerful type inference and generic constraints.
Over the years C# has been implementing these features too, which is good, but they have been implementing them in incompatible ways, which isn't very good. Since the investment in F# has been much smaller than in C#, it hasn't been innovating as fast as C# and has been left behind in some ways.
But it is still a great language and remains broadly compatible with the ecosystem and can deliver equal performance to C# with far less boiler plate
Most incompatibilities can be summarized to "source generators" and tooling that relies on other forms of code generation. This can be fairly easily dealt with by writing a helper C# project with the necessary "glue".
Other than that, is there something specific you have in mind?
F# 9 even supports consuming ref struct generic arguments recently added to C# and plans to introduce their definition in F# itself AFAIK (which is likely going to be a nicer experience than in C#!). It has been doing impressively good job at keeping up thus far and deserves way more recognition for this.
Off the top of my head, async is monstrous to integrate between the two languages. Even when it works it's slow.
This was addressed in F# 6: https://learn.microsoft.com/en-us/dotnet/fsharp/whats-new/fs...
Asynchronous sequences (IAsyncEnumerable) support is provided via https://github.com/fsprojects/FSharp.Control.TaskSeq
F# is a very capable language - a broad spectrum of tasks can be solved by just implementing a new CE, something I could not even think of in C#.
Kind of. It supports the C# Task interface nicely (and has for ages), but it you have a bunch of F# async code then you still have to marry the two somehow, and that's still monstrous.
|> Async.AwaitTask ?
Or is there a particular case that’s problematic? So far I have not seen any interoperability issues around this (even though yes, async CEs are way less efficient than task CEs) but I’m still learning so additional details would be appreciated.
The two languages have two different async abstractions which at their core aren't compatible in general. When that happens in software, one of several possible options will happen. The thing Microsoft chose here is to give an API that looks like it's compatible but which is subtly broken for a nontrivial fraction of code (other common choices include rejecting any level of compatibility, providing more complicated APIs allowing the user to handle the multi-language nuances, or providing an incomplete API which works as expected).
Much like how when one proposes a perpetual motion machine you know there must be a bug in the math somewhere, when somebody proposes an interop between two incompatible data structures there must also be a drawback. Pick literally any feature of the language other than sequentially executing code (exceptions and threads are usually great ways for languages and specs to fall apart), and you'll likely find places where the interop struggles. A few that come to mind:
- That solution is only one direction. Interoperability needs to go both ways.
- The exception interface between the two constructs is sufficiently poorly defined that I'd call it a footgun, and code that won't wake you up at night usually has to do extra work.
- Performance in that kind of wrapping is a nightmare, both from the overhead of additional runtime layers, and, more importantly, because the last time I checked it was basically "sync over async" in a way that made the type system happy.
- Going back to the exception thing, cancellation tokens are especially poorly handled in that interop one-liner.
- They still haven't fixed blunders like ConfigureAwait(false), and the interop code absolutely has to care about that sort of thing in applicable contexts (the worst offenders being any sort of "main thread owns everything" code like current popular GUI paradigms).
This reads as an AI-generated text that is very emotional but provides few meaningful technical details (no examples, unrelated APIs, ConfigureAwait - really now?).
> AI-generated
Be nice please.
> Emotional
I'm not sure if this makes me sound more or less artificial, but outside the use of the word "blunder" there were scarcely any strong opinions, much less emotions.
> Few technical details
You came across as having done enough F# and C# to not need a novel to understand. Yes, those APIs are related to the problem of merging tasks and async between the two languages. 95% of the time you won't care, and 5% you'll hit a footgun. Just go write some multilingual GUI async code with a few cancellation tokens and exceptions and report back with the edge cases you find. The two abstractions are sufficiently different that you will necessarily find some, and if you aren't fortunate enough for your F# codebase to be mostly Tasks instead of async the you'll have to actually care.
Okay, sorry. The repeated "monstrous" and focus on how bad it is (which I genuinely disagree with - it's absolutely fine) rubbed me the wrong way.
> and if you aren't fortunate enough for your F# codebase to be mostly Tasks instead of async the you'll have to actually care.
My expectation is that async-based methods are not exposed to consuming from C#. If they are - this is unfortunate. The ideal state is to modernize the implementation and mainly rely on task CEs. Async CE has its advantages in terms implicit cancellation propagation but flowing down a cancellation token explicitly is usually a more pleasant experience than in other languages. Nothing that stands out as monstrous, and if something is - it has to do with the way a particular GUI framework implements it and not the language and platform themselves.
Last note on sync over async - threadpool can deal with it just fine. It has detection mechanism for this which proactively injects threads when a worker thread is expected to be blocked that bypasses regular scaling through hill-climbing.
You don't really. F# has the task{} abstraction that works just like C# and is fully compatible.
Which works fine assuming all new development is happening in the F# half of a codebase and you don't have a significant amount of old F# async code lying around.
It doesn't require that. You can do a strangler pattern of sorts and use task for the interop layer, then run each call site through a small helper that converts to F# async.
Or, hell, just throw caution to the wind and text replace. I've heard some folks do that and it worked out well for them + wasn't much time to clean up either. They're especially happy to have better stack traces too.
So...it _does_ require that, but if you have time/risk to spare then there exist tenable migration strategies.
The F# team has done a great job of building compatibility bridges back to the C# way of doing things so the actual impact isn't bad.
> There is also a new compile-time error for classes with over 65,520 methods in generated IL. Such classes aren't loadable by the CLR and result in a run-time error. (You won't author that many methods, but there have been cases with generated code.)
The mind boggles.
At any rate, F# is a terrific language. After excel, probably the second best thing Microsoft has ever released. Turns .NET into a reasonable platform.
A few fumbles by Microsoft early on stunted its growth, IMO, but it's a really good and relatively easy to learn general purpose language.
> A few fumbles by Microsoft early on stunted its growth
Microsoft really, _really_ missed out on a huge opportunity to rebrand .NET into something else when they did the Framework -> Core transition. Despite .NET being cross platform for over ten years and winning benchmarks left and right people still see it a the “slow enterprisey framework that only runs on Windows”.
Yes, big time fumble.
I've met a handful of folks who used it in the .NET Framework days and hadn't kept up with the changes in Core (multi-platform, object-functional hybrid, minimal syntax for console and APIs, etc.).
What do you mean by "minimal syntax for console and APIs"?
You no longer need imports, or namespace, class and main method boilerplate to write a basic C# program; there’s now a way to simply write some lines of code in a file with some implicit imports, and compile/run them like it was more of a scripting language.
https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/to...
(If there’s other syntax changes, I don’t know them, AFAIK it’s the same C# you write)
> If there’s other syntax changes, I don’t know them
The new syntaxes that come to my mind are:
* File-scoped namespaces
* "global using"
* "using static"
> What do you mean by "minimal syntax
It's a beginner friendly and "demo mode" set of features to reduce boilerplate for small programs, such that the minimal "hello world" program is literally 1 line of code.
And a working minimal ASP web app with a http endpoint is not much longer.
C# and .NET was from day 1 designed for "programming in the large" so that large codebases can be sensibly organised.
This however causes some overhead, such that a person coming from Python might have looked at the standard C# "hello world" example with using statements, namespace, class and method wrapping the 1-line payload, and conclude that the language is impossibly clunky and cumbersome. My opinion is the opposite; managing e.g. 50K lines of code without those ways of organising code, is going to be impossibly clunky and cumbersome.
However, it is also true that demo mode is great for 1-file demos that get right to the point.
I think the using statements are fine, even for one-file demos. Python, Go and Node don't have implicit imports like C# and it's fine. I'm actually not entirely sold on the implicitness. Forces the reader to look at the .csproj file and the target framework documentation what is implicitly imported.
Most of the verbosity comes from classes and namespaces. Go and Rust have shown it's possible to design a language for "programming in the large" without classes for everything and with less verbose namespaces.
But to be fair, I'm just getting started with C# so my comment above is likely wrong and biased. Happy to be proven wrong :)
C# has several constructs like anonymous types, tuples, named tuples, and records (for structs and classes). Each has different use cases (and sometimes limited scopes like anonymous types) that can serve different contexts for data modelling.
This is, once again, the function of the team and not the language. There is no hard mandate on how namespaces are selected so verbose namespaces is a result of teams preferring it over more concise naming. Part of that might be purely practical. Whereas in JS, you would import a local module by path, C# imports via namespace and convention is to use pathing, but C# will of course allow any namespace convention you like for local modules and does not constrain to strict pathing.> Most of the verbosity comes from classes and namespaces. Go and Rust have shown it's possible to design a language for "programming in the large" without classes for everything and with less verbose namespaces.
The statement that "C# and .NET was from day 1 designed for programming in the large" is true and factual, supported by design documents that pre-date .NET 1.0, i.e. in the late 1990s
The statement that more-lightweight ways have since been developed of approaching the issue is more subjective. But IMHO it is also largely true, as the programming language world has moved on since then. The C# design has been extended a lot, but is constrained by being backwards-compatible. In some ways it is of its time.
e.g. We might not be "entirely sold on implicit usings" but it was a way to get from existing syntax, to a 1-liner "hello world". I think that global usings are fine, when used very sparingly. e.g. I am happy for a test project to have a global using Xunit because it will be used so widely in the project code. But not many more global usings.
I very much agree with your comment. C# managed to evolve and adopt many “modern” features while maintaining backward compatibility, which is quite impressive.
Minimal web APIs are structured a lot like Express (whereas .NET web APIs are more like Nest.js)
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/m...
Having recently joined a company using C#, and with a background using Go, Python, Node, and similar, I was worried about heavy "enterprise-style" APIs. I was happy to discover the new minimal web API while reading .NET Core documentation.
Probably top level statements and minimal APIs from ASP.Net Core
> Despite .NET being cross platform for over ten years and winning benchmarks left and right people still see it a the “slow enterprisey framework that only runs on Windows”.
Do these people I've never heard of outnumber the people who did upgrade but wouldn't have if it had been branded as a separate product?
I’m definitely one of these people. Maybe I should give .NET a chance.
It's quite nice now. Tooling is way nicer than it was 10 years ago. My team is all Apple silicon Macs and we deploy to AWS Arm64 instances. Unit tests in GH run on Linux workers.
Less than 50% of software engineers ever used .NET for anything, so the answer is almost certainly yes?
I encountered this within the last week...
Yep, I literally only learned it was cross platform last year when I noticed it as a package dependency.
Mind you, I’m not a coder, and more just a Linux homelab hobbyist.
I was going to ask what the multiplatform deployment story was like, it seems like you've partially answered it - it seems like you ship it with .NET Core as a dependency, kinda like JRE for Java programs?
How heavy of a dependency is it?
And is there a dependency hell situation, if different programs require different versions of .NET Core?
There are quite a few knobs there. You can AOT-compile the bytecode, as well, and you can ship the whole thing as a single binary: https://learn.microsoft.com/en-us/dotnet/core/deploying/sing...
Size very much depends on what you actually use in the app, but on the order of tens of megabytes.
Also, one of the major changes in the Framework -> Core (now just .NET) shift was making it legally permissible to redistribute the runtime?
So you can ship .NET code with a .NET runtime all in your installer.
Afaik, Framework was only distributable from Microsoft.
Nope, have always been able to ship the redist installers. Just like VC++. https://www.microsoft.com/en-us/download/details.aspx?id=21 (scroll down to 'Installation Instructions')
After all, not being able to ship the runtime with the software to buyers would have been... Bad. In the early '00s
I think I was getting a few things conflated. Is the following correct?
You can't run .NET Framework applications without a system install of .NET Framework.
You can absolutely run .NET (ex-Core) applications without a system install.
The state of .NET Framework is that it's packaged with Windows so technically it did not require an install - an OS shipped with it.
The state of .NET is something that Java wants to reach in a few years or so. There are basically three ways to deliver an application to the users:
- A single or multiple files containing .NET assemblies and a thin binary launcher (single-file mode controls the exact shipping of this) - this is similar to OpenJDK which requires a runtime to be installed on the system (except OpenJDK does not have the capability to put all assemblies into a single file with a small native section for the launcher).
- A single or multiple files containing .NET assemblies and runtime itself
- A single native executable compiled with NativeAOT
The first two options allow to "merge" assemblies into a single file or ship them separately.
Shipping just .NET assemblies without runtime takes the least amount of space but requires a runtime dependency (it's easy to install on practically any system).
Shipping assemblies + runtime can also be done as a single file where there's a sandwich of native code and .NET assemblies. In order for such executable to not take disproportionate amount of space (65-120+ MiB) it can be trimmed which is recommended - this reduces base binary size down to 10 MiB or less nowadays (and grows as you add dependencies).
And building with NativeAOT relies on the same linker-based trimming but produces a single native statically linked executable. This results in the smallest runtime-independent binaries (they start at about 1-1.2MiB) with the best startup latency but comes with a different performance profile when compared to JIT and is not compatible with features that either rely on JIT or on unbound un-analyzable reflection where ILLink cannot determine the exact scope of code that has to be compiled. The ecosystem has significantly improved since the introduction of this target in .NET 7 to provide users with tools to make their code compatible, if any changes are required at all.
Java has been there for years as well.
AOT compilation has existed for about 20 years, it only happened to be a commercial product, Excelsior JET, Aonix, PTC, Aicas, are some examples.
What GraalVM, OpenJ9 bring to the table is free beer AOT compilers, and in GraalVM's case, a LLVM like compiler framework that doesn't exist at all in .NET land.
They also have the advantage of using agents to gather the required reflection data, instead of forcing an ecosystem split of having to rewrite existing libraries to make them AOT ready.
There was the Phoenix project from Microsoft Research, which had the goal to replace Visual Studio infrastructure with a CLI based compiler toolchain, but unfortunely that project failed to gain traction within Microsoft.
Shipping runtimes, which .NET Core introduced for .NET, has been a thing in Java land since they exist.
There is also the ability to create single executables, coupled with jlink and jpackage.
Besides OpenJDK offerings, there is a richness of JVM implementations, each to those having other capabilities, like OpenJ9 and Azul with their distributed JIT compiler for example.
.NET is great and I prefer .NET to Java consulting projects when given the option, however it is no accident that Microsoft has decided to become again a Java vendor as well.
For .NET Framework, you are indeed correct, but in practice this was treated largely the same way as e.g. C++ runtime libraries - you just bundled the .NET runtime with your installer.
This has been moot for a long time now, though, since Windows has shipped with some version of .NET preinstalled since Vista.
JRE for Java isn't a thing since Java 8, the official way is to use jlink to create a custom runtime, make use of jpackage, or AOT compile.
You may still find some JREs, which are created by some distributions that take the stuff out of the JDK to make classical JRE, though.
> it seems like you ship it with .NET Core as a dependency, kinda like JRE for Java programs?
Pretty much yes. (there are also other ways to bundle it)
It's not even called ".NET Core" any more, it's just ".NET", which for version >= 5.0 effectively is the successor to cross-platform .NET Core.
.NET 9.0 will be out shortly - The annual release is expected November 2024, likely this week.
Imagine how much better the branding would be if they had just called it:
Simple logo design.CLI commands would be like:
(Yes, I know I can alias)It's not too late, Microsoft!
It is basically the same as any cross platform language now. They even recommend Docker containers for deployment.
C# is sort of underrated but gets a ridiculously easy ride for actively encouraging bloat and abstraction-ism.
What should be "do a thing" is now design a thing to do a thing then call the thing-do-er IDoAThing.
Alone this would not be so bad but the problem is that you only have this. Extension methods? Fuck off (but don't take them away).
Roslyn is great though.
Its very much a culture thing and not a language thing. C# is very versatile. Go look at Unity code and its a fairly different style. It has its own quirks and there's no such as "the right way" but it doesn't feel like Spring.
Also, extension methods are great. LINQ is amazing and that's partly from extension methods.
Right, but extension methods are a hack to cover up problems with OOP
It's just sugar to turn functional style into .Syntax(), but because of that the tooling is very nice and things like LINQ can be a fluent API instead of LISP.
You can call it a hack but IMO you get the best of both worlds.
Don't get me started on fluent apis lol
"cover up" feels like the wrong sentiment here.
It's adding optionality and in fact, doesn't in any way break OOP at all; it is syntactic sugar.
That's a function of the team, not the language.
Languages bring along a culture. Sone teams buck the culture of the broader language ecosystem, most do not. IMO, the C# culture is closer to enterprise Java than it is to Typescript, for good or for ill.
More than culture, language is a statement of values.
There's reflexivity and path dependency but the culture is downstream of the values for the most part.
Here's a sample from the MongoDB client: https://github.com/mongodb/node-mongodb-native/blob/main/src...
Here's a sample from Storybook: https://github.com/storybookjs/storybook/blob/next/code/core...
Here's a sample from Prisma: https://github.com/prisma/prisma/blob/main/packages/client/s...
Here's a sample from Cal.com: https://github.com/calcom/cal.com/blob/main/packages/core/Ev...
These are all fairly mainstream repos so it's not a one off to see "enterprise" style coding in TypeScript.
It's rather the specific use case that determines the style of the code that's written. In the examples above, there's no need for the teams to choose JavaScript classes and inheritance to model the logic, yet it likely better fits the programming model of those modules.
Look at Nest.js as well: https://github.com/nestjs/nest/blob/master/packages/core/rou...
The whole codebase of Nest.js looks an awful lot like Spring or ASP.NET (controller syntax) probably because there's a lot of crossover in terms of what APIs need at scale and how to logically organize that logic.
It just so happens that most use cases for C# and Java favor applications at a larger scale. Another key difference being object scope and lifecycles -- something that rarely comes into play in the generally single-threaded Node runtime. This being one of the key reasons why dependency injection is a thing in C# and Java land.
Both. The language can afford that usage through the design decisions.
I kind of think the best languages would be those that make creating layers of spaghetti indirection nearly impossible. In practice, it's usually much more specious. Names like "CreatorImplementer", "FactoryAbstrator", "DeviceAccessory" and a half dozen equally amorphous things calling each other as if Thomas Pynchon was writing software.
I think I've seen it in all languages I've worked on professionally. Maybe not in Perl.
I mean, you can do the same in JavaScript and TypeScript as well as any number of other languages.
Agreed. There's plenty of obtuse indecipherable JavaScript these days.
I keep remembering this code I saw in 2014. It was in ember and used mongodb and some bizarre ember features I've long forgotten to stylize things instead of conventional stylesheets.
I had been doing JavaScript since it was called LiveScript in 1995. I knew it very well. This setup was utterly impenetrable. Charging a button color required I think 6 or so different data stores to be manually updated and placed back in alignment because things were validating and cross checking for no good reason other than being a complicated pile of tangled messes. I had to quit the job.
I know how incredible this sounds. I can prove it. He does everything like this. Here you go https://github.com/davideschiera/flame-ui/tree/master ... I'm not quite sure what these 252 files do but I'm pretty sure it could be done in about 25 lines of code in a single function.
I had worked with him previously on a C# codebase almost 20 years ago now. I remember him being just as insane but somehow the language successfully encapsulated the insanity so I never had to deal with it.
There's this fundamental clash. I think things should work, be maintainable, and have the most minimally complex implementation possible - the Ernest Hemingway approach. Some people prefer James Joyce.
Human behaviour in programming is of course totally independent of language and tools, indeed
It is, in fact, independent. You can create horrible monstrosities in any language, or create great code in any language.
Sadly modern idiomatic C# is a bit of a nightmare these days. It's a heavily abstracted/"indirected" style, where you are basically forced to use a debugger to determine where a method call actually goes. The goal seems to be to make things as emergent as possible, with reflection and IL emit determining code flow as much as possible.
I think that F# goes a long way to prevent this, by nature of being functional.
I’ve written a lot of modern C# and I don’t know what you mean - especially with regards to your comment about F# being different.
Are you taking about the popularity of the mediator pattern (via Mediatr)?
The default C# style has always preferred a large dose of unnecessary runtime flexibility. Sometimes that comes in the form of lots of dynamic dispatch, sometimes it's DI, sometimes it's non-sealed overly large base-class types, and sometimes its some combination thereof - but the net effect is that it's not possible to reason about code by just reading it. Tooling becomes worse and/or less valuable because tooling too cannot reliably trace all possible code paths.
Additionally, Microsoft has an outsized influence on the C# ecosystem. As a result, they wrote quite a few architectural and style guidelines that, naturally, target themselves. It makes sense for a hyper-widely distributed library to consider things like binary compatibility as critical features, but the design rules that follow from that have zero utility for apps, and low utility for most libraries. The C# ecosystem also has an unhealthy veneration for MS provided code. While I'm sure most of the authors are quite competent, there's less experimentation and fewer hidden gems because the ecosystem is tilted towards being followers behind the framework providers path.
As a result, it's much harder to refactor code in that style than code in a less (runtime) flexible style, and it's worse when the libraries heavily used are frameworks that have so many extension points. There's a tradeoff between runtime flexibility and codebase flexibility, and the default .NET C# style heavily leans towards runtime flexibility, which isn't a good tradeoff for most software.
Maybe you're just extremely lucky.
MediatR is part of the problem, yes. Way too much attribute/reflection magic there.
One particularly good example is: attempt to add full support (i.e. query text generation) for PERCENTILE_CONT to EF. That will give a good idea of just how bad the idiomatic C# style can get. Now take the EF style of code/architecture and apply it to an entire product codebase - which is completely a understandable mistake to be made (it's not unreasonable to assume that EF would be a good example of C# code). You know you have a problem when you throw in the towel and have ThingDependencies classes all over the place. The aspnetcore internals don't vary too much from the abomination that is EF.
"Just don't code like that," you might say. The problem with that argument is that pre-existing code exists, people don't code in isolation, and the cargo cult has a ton of momentum behind it.
> your comment about F# being different.
You'll struggle to write that style of code in a functional language. There is such as thing as healthy friction. F# has its own problems, but this is not one of them.
Ugh that sounds like the Spring effect. I really like Java but Spring and IoC/DI makes it very complicated.
What in particular do you find difficult about DI?
My experience is that it's "fire and forget"; never give it much of a thought at all and isn't much different from:
Except that DI allows "importing" at different levels.One of the reasons I use Java is for type safety. What's the point when your app compiles OK but then doesn't start because Spring can't satisfy all the dependencies or choose which one. I spend hours trying to get the config right. You end up programming via config.
> I feel like C# is quite underrated
That’s fair, I consistently underrate it myself. Last time I worked with C# was a big messy codebase written by mostly junior developers who didn’t understand what entity framework was doing behind the scenes. I came away from that experience with the impression that C# is a terrible language.
There’s an interesting problem with “practical” functional languages that we don’t really know what a crappy enterprise F# / OCaml / Haskell codebase looks like - though maybe Haskell is sufficiently popular in crypto these days that there’s some examples. But generally software in these languages is written by highly motivated developers with a lot of flexibility. OTOH a lot of bad C# and Java is written by developers with bad technical management who treats devs as code monkeys. Further a lot of the “technical enablers” of bad C#/Java are bespoke enterprise frameworks that simply don’t exist for functional languages. But in principle, enterprise Haskell could be quite vulnerable to a “productivity-boosting” language extension that locks older Haskell codebases into horrible typeclass golf.
I say all this as someone who loves F# and would only take a C# job if I needed the money. But certainly a lot of the enterprise antipatterns can be clumsily implemented in F#, while a well-written C# codebase is usually pleasant and productive. In particular F# doesn’t totally escape problems like “the Nuget service locator is overengineered for our needs, but we are on a deadline and an excessive abstraction is better than hard-wiring everything.”
My hypothesis is that F# is somewhat self selecting at senior/lead dev's who would have a lot of choice in the language that they get to choose not the army of "code monkey's" as you call them. Generally this would mean higher code quality on average.
C# these days is GREAT, but F# still has features that C# doesn't like unions and much more powerful (and ergonomic) pattern matching.
And line for line, F# is more effective at getting things done (and i would argue with less bugs)
The c# team is supposedly putting in serious work on Discriminated Unions. I'm really interested to see how it turns out because f# already does a great job with them.
Discriminated unions is probably the last "must have" feature I've been waiting for in C#. Better pattern matching would be nice to have but honestly not as much of a gamechanger.
The C# team for several reasons decided "rock solid" pattern matching was a higher priority than discriminated unions because good discriminated union code needs good pattern matching. In the last few versions of C# I think pattern matching has gotten really good. List Patterns finally exist now, and that influenced the new iterable collection initializers, which are also pretty good now. Last I checked, the C# team was still debating Dictionary/Hash key lookup Patterns and Dictionary/Key-Value initializers, and that's maybe my last big request in pattern matching other than native discriminated unions, of course.
What is also great about C# is that the language server for editor support uses the real compiler. I once set that up with emacs and it is by far the best experience I had in emacs with any language.
> relatively easy to teach to anyone that is already familiar with JS or TS.
What about Java?
In my experience having mentored devs into F# on very large projects its easier to learn F# if coming from a JS, Python, Go, etc background than C# or Java even without previous .NET exposure. Not due to a skill issue, but more of a psychological one.
IMO to be productive in F# code code tends to be simpler - many patterns that dev's valued learning in a OO context become a little obsolete or less needed as a whole. This is my anecdote but it is the C#/Java/etc crowd that tend to have the most negative immediate reaction to F# - they ask "where is X" and often I immediately say "X is bad, you shouldn't need X - think about why". They associate X with some kind of power/productivity gain, but don't realize there is many ways to do things. Kind of follows the paradigm of "frameworks/patterns are missing language features".
If they keep at it then it eventually clicks but there is almost a psychological "unlearning" that needs to be done which at least in the C# camp creates an almost tribal tension between the two camps - its just too unfamiliar. Whereas for example TS dev's seem to find it somewhat familiar in parts; even people only ever done JS previously I've found have become good F# dev's. Could be because the flow more suits the way you would code in a dynamic language while still being strongly typed (e.g. REPL's, type inference, static modules, functions over classes, etc).
Vouched for this dead comment. If this really should be flagged to death, could I get a quick refresher on what parts are boring reading or off-topic? The comment is an anecdote and not data, but this does not seem like a flamey post, or a low effort post, or a strawman post.
Probably automated. If someone creates a new account then immediately posts big comments, lots of comments, or links, the comments end up dead by default and need vouching. The dead spam comments often found at the bottom of threads are why.
C# and TypeScript are remarkably similar owing to having come from the same designer. But even before then, it seems as if the two languages influenced one another.
Check out my small repo here showing how similar they are: https://github.com/CharlieDigital/js-ts-csharp
Many of the ideas trace back to Turbo Pascal, Delphi, and J++.
F# is much easier to teach and learn than C#.
>At any rate, F# is a terrific language.
Any downsides, in your opinion?
I had toyed with it a few years ago, using that lightweight IDE called LINQPad, IIRC, but did not follow up, or keep track of its pros and cons or progress later.
https://www.linqpad.net/
Downsides: lack of jobs, libraries, learning materials, small community, little promotion by Microsoft outside of "what's new in F#" and other announcements.
It's a shame because F# is a beautiful language — it's fun to read, write and maintain. .NET now works well cross-platform with editors other than Visual Studio. General perks of .NET like cross-platform compilation are a nice bonus, for example, on macOS I can:
To get self-contained binaries (that don't need .NET to be installed locally) for those platforms.There is a decent multi-platform UI framework called Avalonia: https://github.com/fsprojects/Avalonia.FuncUI
There are good actively maintained web frameworks: https://fsharp.org/guides/web/#web-frameworks
There are some passionate people writing great books about F#: https://fsharpforfunandprofit.com/ddd/
Most of F#'s adoption issues seem to be because:
1. It's tough to convince people outside of the .NET community to use .NET.
2. Most of the people using .NET use C#.
3. It's hard to convince C# users to adopt F#. (See https://www.reddit.com/r/dotnet/comments/16m0wdj/why_dont_yo... and similar threads.)
I'd love to work with F# full-time, but — short of starting my own company using it or convincing Microsoft to pay me to help them showcase its benefits — it's been hard to find work with F#, which drove me to spend time with other languages.
F# can also be compiled with NativeAOT. The main thing this target complains about is printfn and especially printfn "%A" which uses unbound reflection. So as long as you just Console.WriteLine instead it may not emit warnings:
Note that -c Release is no longer required - it is now a default for 'publish'. Also, if you are doing a self-contained/non-AOT build, you may want to do it as follows to retain compact binary size: All the properties above can also be defined within respective .csproj/.fsproj files inside PropertyGroup blocks as e.g. <PublishAot>true</PublishAot> so you don't have to add them to a command every time.F# also has excellent support for scripting with fsi and .fsx format: https://learn.microsoft.com/en-us/dotnet/fsharp/tools/fsharp...
Someone even made a tool for it which builds on top of bflat allowing to compile F# script files into native binaries directly without project setup: https://github.com/ieviev/fflat
Thanks for the tips! I had thought there were still some other gotchas with AOT and F# but it looks like the list is smaller than last time I looked. https://github.com/dotnet/fsharp/issues/13398
Since F# is primarily meant as a functional language, and thus is written as such, learning it through examples when you have no functional language knowledge, there is quite the learning curve.
Compare that to starting to read Python when you come from C#, it reads as English.
F# omits much more in its code.
You know how seasoned lisp programmers say the parentheses eventually disappear? I felt like F# was the opposite. There were invisible parentheses everywhere and once I figured out where they were, the language made a lot more sense.
Most ml based languages feel like that.
It feels like typed lisp with the brackets omitted.
But you can see that about anything with an arbitrarily complicated grammar. If you have internalized the grammar, then you can place parentheses into any utterance to show the structure, which means you can see where they would go even if you don't actually write them in.
Yes, the language make a lot more sense if you know how to do that compared to if you don't.
> anything with an arbitrarily complicated grammar
But that’s the thing about F#: the grammar isn’t arbitrarily complicated. It’s really quite simple, which is why mentally inserting lispy parentheses helps me reason about it.
I persoanlly found the syntax weird and the IDE support poor when i was playing with it in 2013
Both of these are surprising to me. It is an indentation language, similar to Python which I love. And it was by Microsoft, which makes Visual Studio which I also loved
Yet I found the syntax a lot odder than Python: multiple ways to call methods (C# style and Ocaml style), a wide selection of strange brackets, `.[i]` for array lookup instead of `[i]` (i think they fixed this eventually). And despite Visual Studio being great for C#, its support for F# wasnt up to scratch
I ended up settling on Scala, which is semantically very similar, but with an easier syntax (not perfect, but better) and decent Intellij suppory (again, nowhere near perfext!)
The biggest difference happened between 2020 and 2024. I encourage you to give it a try - F# support with Ionide in VS Code is excellent.
There is no other platform like .NET in capability to cover both high and low level scenarios.
f# has improved a ton in the last decade. Rider and Ionide are both good environments to work on f# code in.
Not necessarily my opinion, but here is an interview [1] about someone who gave up F# in favour of Odin ... maybe of interest here in the subthread
[1] https://odin-lang.org/news/newsletter-2024-10/#interview-wit...
I feel like going from one niche language to another even more niche language is risky, even if the performance improvement demonstrably true.
I suppose it’s a chicken and egg problem for languages that want to grow their communities (if nobody takes risks on a language it won’t see adoption) but I’m surprised given their problem domain they didn’t go with something more widely known
Not sure what's up with the author but from our brief interactions on X I have gotten an impression that they did not understand performance at a sufficiently low level and implications of using each language (and their compilers!) despite being adamant about the opposite, resulting in technical decisions driven by inaccurate conclusions. It was quite unfortunate and seemed like an emotionally-driven choice.
It's perfectly fine to use an LLVM-based language where it matters, but it's sad when C# could have been used to solve the problem without throwing the baby out with the bathwater.
Thanks to those who replied. Good info. That cross-compilation feature is cool.
The lack of jobs, that is only downside. Would use it all the time if I could, but not many companies want to switch.
I've found when I do get the chance to use F# it is in .NET shops, where the tech staff have agency to make decisions. Language deciders, vs language takers. Those places typically just "choose to use it", the places I've been in generally won't advertise for it however.
IMV it thrives in back-end scenarios where correctness, and algorithmic work could occur whilst still needing some speed of development and a large enough ecosystem. It typically doesn't live in large teams hence less jobs but the teams that I've been on that use it have far reach/leverage once they are proficient at it. I've been lucky to be a part of more than one team like this.
Other languages do this too as well (Rust, Go, etc) with different tradeoffs - F#'s advantage to me is if you want easier learning of staff, correctness, interoperability, faster development IME, scripting at times and reasonable performance (e.g. C#/Java/etc class). It's rare you feel you are fighting the language or producing boilerplate or repeated code patterns; at least in the domains I've used it in. At least in my use cases it is the jack of all trades kind of language; that is its problem as well as its strength.
The push back I get is unfamiliarity and training. Everyone is comfortable with Python. Even if in my mind, F# is great for all the same tasks, it's too big a leap.
We are spoilt with a lot of good tools these days, F# being one. As an ancedote a hybrid F#/Python shop is one use case where I have seen F# thrive. My personal view is that F# is actually better than some other langs for when you want to convert that Python code into something that runs "faster" but still looks somewhat like Python to stakeholders (I personally find F# easier to read than Python for example the generator syntax in Python to me reads backwards). This typically happens at least for me when the business team/data team/quants/actuaries/etc use Python but want you to productionise/scale it. Static typing, and lower level optimisations possible + easier access to multi-threading. I've personally converted from Python to F# models from a math background and seen 100x performance - as a bonus I can talk to the F# code and typically they understand/can read it (even if they couldn't write it directly) vs say C#/Java with a non-whitespace syntax. Allows easier verification.
Well, I made a large, multi-year company/technology bet on top of F# with Phosphor.
After over a year of trying to make it work, we completely rewrote the application in Typescript and Rust. The product we're building is an end-user programming tool where the traditional lines between front-end and back-end work blur, and the .NET ecosystem really didn't lend itself well to this.
Our hope was that we'd be able to use the Fable library to maintain type safety across a variety of technologies while being able to interop and in and out as needed: Fable compiles F# code to JS, Rust, .NET, etc. The reality was that the interop story between the various libraries we'd use was much harder to achieve than initially expected, and managing and updating multiple dependencies and their bindings was an absolute PITA.
The assumption that F# makes for beautiful, efficient code is still a safe one, I think. But the ecosystem and the way that it was designed makes it (in my view) applicable only for applications and software that have very traditional frontend/backend lines. F# would be used for the back-end only in that case.
Today, two of the technologies I'm most excited by (and which we're using internally) are the Effect library, whose Schema library fills in a lot of the gaps in TS's type system, and Moonbit, which is what I imagine a modern version of F# would like, free of the MS/.NET dependencies. Moonbit is really, really well designed (designed by the creator of OCAML's version of Fable, ReScript), and it compiles directly to highly optimized JS, WASM or Native output. We're using Effect in production, and not yet Moonbit, but the promise Moonbit holds as a language built for an AI-first world is pretty fantastic.
I’ve had a good experience exposing F# code that compiles to Typescript modules. The approach was to write the core business logic and validators in F# and the rest of the front-end application in Typescript and the back-end in C#. That is, core business logic and validators in F# and all of the IO in TS and C#.
Is your company related to Darklang by any chance? I recall they were a product similar to yours, written in F# as well, but I haven't kept up with them recently.
Effect is pretty nice, I wish it existed in other languages than TypeScript too. Regarding MoonBit, it seems like its own proprietary programming language so I'd hesitate to move to that over something well known, what are your thoughts on that aspect?
Compiler is open source. We're not betting on it yet, but following closely. Rate of positive change and shipping is really something to see.
Had a crypto class that let us choose any language that used .NET. My homework in F# was so much easier to read than everyone elses. Would def use it more, but nearly 100% of my data science work is Python.
That’s so cool.
Note - F# 9 also benefits from practically all performance improvements in .NET 9 itself, especially around object escape analysis:
https://devblogs.microsoft.com/dotnet/performance-improvemen...
I really miss working in F#, such a productive language. But I still enjoy keeping tabs on the updates.
I always thought the tooling was quite good, given the size of the community and Microsoft's apparent apathy towards it at times. The biggest pain point for me was accuracy in code test coverage.
I only toyed with F# recently, but coming from Python I really like that I can also use a REPL to experiment stuff: I wonder if experienced F# devs use it too.
I hope to have time to build a small web backend project this winter, to get to know the language and ecosystem better.
I heard good things about Oxpecker for the http part; do you have any recommendation for a good postgresql client/driver ? (I don't like ORMs)
Cf: https://lanayx.github.io/Oxpecker/
Here are 2 possibilities:
- https://monazita.gitlab.io/monazita/ (i developed it for a project of mine while learning fsharp. Basically works but could be more polished and is pgsql only)
- https://github.com/jacentino/DbFun (more polished than previous project, and multi db)
Npgsql is the popular C# driver, and has an F# wrapper. I'd start there.
Thanks :)
As an polyglot dev who has the chance to use F# dev at times the REPL is how I POC most things - if I'm not sure of an API, even inlined into my actual codebase just hit the "Send to F# interactive" and it runs there and I can try things inside a module. Or when I want to try a new library, or when I want to do a quick benchmark, or as a substitute to a PS script, etc. You can even make F# scripts executable with the shebang `#!/usr/bin/env -S dotnet fsi` which we do a lot for scripts around our projects as an alternative to bash/python in our .NET projects where we already know a dotnet-sdk is installed. Usually runs faster too and typically just as concise. Just my anecdote opinion - the syntax and idioms of F# lean themselves more to REPL programming then C# which typically needs more OO structure rather than small code snippets strung together.
How does the versioning for F# work? These seem like a bunch of nice quality-of-life improvements, but a major version change doesn't seem warranted in terms of semantic versioning - no breaking changes - nor in terms of a major leap in terms of language features that a non-semver project might use as a reason to go version 8 to version 9.
Another comment mentions .NET9 that recently came out, is that the reasoning here, to keep in stride with .NET version numbers?
So .NET and C# have moved recently-ish to yearly single digit incremented releases and I’m guessing F# is doing the same. Not sure if they intentionally made the switch when it would line up with the .NET version or if that’s just a coincidence. C#, for example, released version 13 for .NET 9.
For both C# and F#, the version of .NET indicates the version of the build tooling (msbuild, compilers, nuget packages, etc...) that will be used to build the code whenever you invoke `dotnet build`.
That means, for example, that the F# team _could_ have shipped a language change between versions 8 and 9, but if they didn't then at least they could have shipped a compiler change or an msbuild change that required .NET 9 for whatever reason.
For developers, usually you can just update your code to the latest available runtime version unless you have to wait for your deployment environment to have the latest .NET version installed. Nowadays that's not necessary due to MSBuild changes to create self-contained deployments, but something to know.
C# will deliver the 13.0 compiler with .NET 9.0. It is a fun coincidence that F#'s 9.0 release coincides with .NET 9.0.
A new NET version comes out yearly and the C# and F# versions align with the yearly version but C# is ahead by 4. Semantic versioning is overrated anyway.
It is fun to point out that the VB.NET compiler is still at least 3 versions ahead. As far as I am aware VB.NET hasn't had a major version number bump since 16.0 (around .NET Core 3.x or .NET 5, as I recall), but the VB.NET compiler still also claims the 6 versions before .NET.
C#'s (lucky) 13.0 in .NET 9 is also just partly related to the non-linear version numbering of .NET itself with the long shadow if .NET 4's many minor releases that were also "major", the Core divide, and the eventual "unfork" at 5.0. F#'s 9.0 matching .NET 9 is a fun coincidence, and also just because F# is younger and missed some of the .NET fun like 4.x "minor number is as important as major number" years.
What is the state of F# a replacement for C# to create GUI apps on Windows? Are there companies using F# for this purpose?
There are a few options:
https://github.com/fsprojects/Avalonia.FuncUI
https://fabulous.dev/ (which targets Avalonia/MAUI/Xamarin)
https://github.com/kekyo/epoxy (Avalonia and WPF)
> Are there companies using F# for this purpose?
I can ask around if you're interested.
I'm in this world, few companies are doing GUI apps at all these days. Most have long converted to Web Apps.
Since it's pretty tiny minority of .Net users use F#, I'm sure someone somewhere is using F# for GUI but it's going to be tiny shop somewhere.
Appreciate it, thanks!
I have never done F#, but when I was looking around it this seemed like a greate resource: https://fsharpforfunandprofit.com/
That is indeed one of the best sites for people new to F#, both experienced C# devs and those relatively new to programming. It's rarely updated any more, so you won't find discussion of the new F# 9 features there, but the existing articles are excellent for getting your head wrapped around concepts like `apply` and `bind`.
How is the F# story on Linux/MacOS?
Is it like a windows thing that you can also do on Linux or does it feel like a fully supported language?
F# is full supported on Windows, MacOS and Linux via CoreCLR and several more platforms via Mono, just like C# is
Never noticed anything odd using in in linux.
Same as C#. Anything that doesn't rely on windows libraries (so like various GUI libraries from MS) works fine on Linux and Mac.
A great programming language, pity that managment isn't that supportive of it, always feels like they consider it was a mistake adding it to VisualS Studio 2010, given how little of Microsoft own frameworks and Visual Studio tooling have first class support for F#.
So we are mostly left with the community doing most of the work in a company with enought money to burn in deals like the Actvision Blizzard one, but not to staff the F# team appropriately (and they aren't the only one in such a state).
Perhaps it could have been treated better, but then if you compare it with Kotlin, Scala or Clojure - F# ships with the main .NET SDK. It does not need to be installed separately, no user action is required to jump into an F# project immediately or to add an F# project to an existing solution. You can simply dotnet new classlib/console --language F#, dotnet build and it is ready to go.
It could use more marketing, halo projects and word of mouth though.
Kotlin and Scala don't belong to any JVM implementation vendor, like F# belongs to Microsoft.
And if consider Google to be a platform vendor with hands on Kotlin, then Android / Kotlin development experience is certainly much better than F# has ever achieved since its Visual Studio 2010's introduction.
What F# has going for it, despite everything, is its great experience on Windows, versus Haskell and OCaml, which remain UNIX centric at heart.
Should one use the result type for handling errors or exceptions or both? What's the rule in F#?
When should you prefer immutability over mutability since both are possible in F# and it probably has a measurable impact on performance?
When should you use objects instead of records or unions in F#?
Since mutability is possible, any reason to not allow an explicit return or continue? It makes certain code patterns much less nested and easier to read.
louthy/language-ext [1] is a C# library which does its darndest to build a fully functional language inside C#. I found it very interesting and used it for an experimental project (which ended up failing, not sure if my usage of language-ext was partially to blame). People told me I should have just used F#.
Anyone familiar with both that can compare them?
[1]: https://github.com/louthy/language-ext
The Tail Call attribute is a huge improvement. TCO is a requirement for correctness and now we can enforce that at compile time!
I wish every language with TCO supported some way to tell the compiler "if this cannot be optimized away I want an error."
All it takes is a moment of inattention to screw up.
Are there large, real world/OSS SW that uses F#?
Jet.com. They were a (little) competitor to amazon before they were bought by Wally-mart. They are replacing the F# with Java though: https://news.ycombinator.com/item?id=39592760
Its weird they didn't switch to C# - that would have been a much smoother transition to a memory managed brackety language.
The rumors I've heard is that it is the "Oracle Golf Club Handshake Problem" in action: the current C-Suite of Wal-Mart got wined and dined and golf outing-ed a bit too much by Oracle who wanted a large contract expansion with massive revenue boost (for Oracle) and were promised a bunch of software resources/troubleshooting/consultant time in the next contract if they switched to far more Java. That sales job turned Java into a top-down mandate. It is a fun modern version of the "Nobody went wrong ordering more products and consulting from IBM" problem if IBM had an even more ruthless sales department and also owned too much of the database industry and too much of the Payroll/Time & Attendance industry (and worse, their legacy purchasing decisions and sunk costs) because no one has cracked down hard enough on vertical monopolies in the last century.
Allegedly, of course. I'm sure Wal-Mart PR will happily tell you it was their own idea and how Java is the language of the future again, or something to that effect.
Also, whether or not the rumors hold, it is hard to discount the pure Occam's Razor that it was just Wal-Mart's own idea to cut costs because of the simple dumb fact that labor costs in Wal-Mart's home labor markets for Java developers are certainly cheaper than .NET developers in the same job titles/experience levels. A mandate to switch to Java certainly was one cheeky way to do a slow layoff of expensive laborers without doing a real layoff.
While it is fashionable to hate Oracle, they aren't the only Java vendors in town.
As someone that does consulting across multiple programming languages, what I notice is that .NET is too late to the party in the server room wars.
For better or worse, UNIX and ideas derived from it, won the server rooms and cloud deployments, even Azure is mostly Linux nowadays (> 60% as per official numbers).
Due to its previous history, .NET has always had a bad name in UNIX shops (even with Mono), and most companies would rather use something else, unless they are a Microsoft shop, it could be Java, Go, or whatever.
As far as I am aware, Wal-Mart isn't a Microsoft shop and only acquired Jet for the business, not the technology, so like in all acquisitions something like that was bound to happen.
For many years the Wal-Mart purchase of Jet was lauded as a brilliant "acquihire" by Wal-Mart because they had lagged so far behind Amazon technically and Jet gave them the bootstraps to better compete. It has been said that the modern, competitive parts of Wal-Mart's tech platform is largely owed to former Jet engineers and their .NET (especially F#) forward development ideals.
I've never worked for Wal-Mart, I've only seen PR and blog posts and job postings. That is likely its own spin of what happened, too. But during those years Wal-Mart was seen as a stable "Microsoft shop" in terms of what they were hiring for in software developers (if you didn't mind relocating to Arkansas).
Externally, the Java thing did seem to be a "coup" that came out of the blue. Like I said, the easiest explanation is that Wal-Mart, a company famous for ruthless cost cutting and layoffs, felt like they had milked their acquihire of Jet for just long enough to stay competitive and decided the easiest "exit" for those expensive employees was to force a top-down mandated tech stack change to a known cheaper tech stack and gently encourage them to quit on their own initiative in the chaos. (In that Wal-Mart was already very quick to RTO, they had already played that particular card, many other companies are still palming to cause similar layoffs.)
The Oracle rumors are icing on the cake, sure. But also extremely plausible given that is how Oracle's business works today.
NRK, the Norwegian state television runs their web and streaming platform on F#.
https://tv.nrk.no/
Not pretending my project is large or important, but it is an example of a non trivial project written in fsharp: https://gitlab.com/myowndb/myowndb/
For WebAssembly, there is Bolero:
https://fsbolero.io/
Unfortunately, maintenance seems to have stopped since Jan,
https://github.com/fsbolero/Bolero/releases
(I tried creating a sample project on Linux which didn't work to begin with)
I dont know, i have used F# for a while now. My problem with it- is discoverability and reuseability of the created code. It is very hard to read and get in - compared to classic OO and its hard to reuse something created and custom tailored. My 2 cents, may be worth even less, due to my limited experience and a propably wrong approach by me.
It takes a while to shed one's existing OO expectations, but after that, I think you'll find well-organized F# code to be easier to use than C#. Immutability and lack of side-effects makes it much simpler to reason about what someone else's code is doing, and then reuse it.
If you have any specific examples of discoverability/reusability issues in F#, I'd be curious to hear them.
Rider's recent free non-commercial license makes F# a much more attractive option for macOS, I think
I love F#, but it depends almost exclusively on Microsoft, which is not a main priority. What if Microsoft decides to stop developing it?
It's an open-source project with its own F# Software Foundation. If Microsoft drops it, I think it would continue.
https://fsharp.org/
What’s new in F# 9? Well of course that would be G#.
It seems insufficient to accord you only one upvote for this.
Since the null-related blog post isn't published yet, anyone here knows the reason to implement null rather than write middle-layer in C# for whatever you want to use?
At this point, almost all community and all standard library APIs use nullable reference types (and types that aren't NRTs are obviously expected to always have a value).
This change in F# makes it able to interoperate with C# NRTs, where C#'s T? becomes F#'s T | null and vice versa.
I feel like the ease of integration of F# into existing codebases is one of the major selling points. You don't need to write wrappers in order to consume most existing C# code, which, for example, enables writing a core with business logic in F# while keeping the IO in C# if necessary.
F# has chosen this approach of implementing compatibility with newer C# features such as support for ValueTuple<T> and Span<T>. It has been pretty successful at keeping F# inside the compatibility story.
---EDIT---
Mean to post this under the GP
> let notAValue: string | null = null
When did F# get unions?
Damn. It's only for nulls: https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-10...
Wonder if they will generalize: https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-10...
At least they chose a syntax that makes it possible to generalize, which is a smart choice imho :)
Yeah it looks a lot like Python's PEP 604[0].
[0] https://peps.python.org/pep-0604
It had it since the first version as far as I know.
You're probably referring to Disciminated unions aka sumtypes? That's not what the parent meant
I thought those are the same and I realize that there is a difference.
Kinda disappointed that they allowed null into the language, they should've just made some syntactic sugar or some auto bridge to reinterpret them nullable types as option types. Well, too bad. Not that I was a huge F# user anyway :D
That would be a breaking change; certainly it's impossible to do that in the presence of reflection, and I imagine there are simpler cases. (See discussion on the similar https://github.com/fsharp/fslang-suggestions/issues/884 .)
[flagged]
This looks like a good set of improvements but goddamn F# is an ugly looking language
To each their own, I guess. I find the various modern ML-influenced languages to be among the best looking.
I agree with you. The best looking ones are Gleam, ReScript and F#
I always miss pipeline operators in languages without them after F# exposed me to them.
Really? It's a classic ML language. What about it specifically do you find ugly?