I get the feeling F# is a second-class citizen in the ecosystem. How likely is it (or is it at all possible) that I'm going to run into some library that doesn't work with F#?
It's impossible for a .NET library to not work with F#. It's very possible (and even likely) for a .NET library to prevent you from writing idiomatic F#.
> It's impossible for a .NET library to not work with F#.
This isn't true, C# has been adding new ABI features that didn't interact correctly with F# until the compiler and tooling catched up. For example, the spanification of C# was a huge a pain point and it still is when it comes to tooling.
You mostly have to look at the time frames between C# adding features with new ABIs and the time it takes for F# to announce they can actually consume them.
For example, F# used to crash the CLR with InvalidProgramException when setting C# `init` properties. [1] It took almost three years, I think, to release the fix.
Another rough example would be spans, and their more general feature, byref-like types (ref struct). These required plenty of compiler support, as they've got special lifetime rules (and more pending to implement `scoped`), they are banned as generic type arguments, and they require ignoring a special Obsolete attribute.
While these were added to F# timely, many language features still break when they interact with them: local functions, computation expressions (even the built-in ones), recursive type inference, and generic intrinsic methods such as `raise`, `defaultof` or `typeof`.
This wouldn't be so bad if C# hadn't "spanified" the shared framework and the entire ecosystem without CLS alternatives for many APIs.
So, these natural F# snippets don't compile:
let numbers = [| for span in text.EnumerateLines() -> Int32.Parse span |]
let checkNonEmpty (span: ReadOnlySpan<'T>) =
if span.Length > 0 then span else failwith "Expected non-empty span."
Edit: And I haven't even gotten into the libraries that use reflection expecting you to declare types in ways F# doesn't support.
Stuff like Roslyn analysers, code generators and interceptors, only take C# semantics into account, the first one also works with VB.NET.
Since its introduction that CLR has the notion of CLS, the Common Language Subset, that any language targeting the CLR should be able to understand.
Anything in the MSIL or metadata, that isn't part of that, requires additional effort from the respective language to be able to expose those features, and many of the more recent improvements, are more in the sense of CLR meaning C# Language Runtime, than Common Language Runtime.
Just to add to the other comments, in my experience a large majority of libraries work fine. Maybe a little tweak here or there, or maybe you need to use some C# esq syntax (which is harder in my case because I so rarely do now).
Generally though, it's pretty easy. Sometimes you'll want to make bindings around various things (not that you need to, but it'll make things smoother).
It's not actually awful, it's just that there's no real documentation of "hey today we're going to take Serilog/whatever and show you how to configure it in F#" style videos, so depending on where your weaknesses lie it can feel frustrating.
My experience mirror yours. I really lean into the functional core, imperative shell because of this. Im mostly interacting with C# flavored libraries at the edges and I don’t try to force F# paradigms there, I just roll with the punches.