GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — `Discussion`: `FSharp.Core` breaking/incompatible changes · dotnet/fsharp · Discussion #16231

Discussion: FSharp.Core breaking/incompatible changes #16231

Pinned
vzarytovskii started this conversation in Ideas
`Discussion`: `FSharp.Core` breaking/incompatible changes #16231
@vzarytovskii vzarytovskii
Nov 6, 2023 · 25 comments · 73 replies
Return to top
Discussion options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii
Nov 6, 2023
Maintainer

Would like to start this discussion for how should/shouldn't we be introducing breaking changes to core library.

Why: Things like the issue below, are reasonable and benefitial for average codebase but can be/are breaking runtime behaviour:

  • [Breaking] Avoid boxing on equality and comparison fsharp/fslang-suggestions#1280

Overall rationale is: this probably should be done eventually if we don't want core library to stagnate and lag behind in features.

One of the solutions is to use shadowing:

  • Introduce new breaking logic to standard Module/NS in FSharp.Core (be it prim-types, array, seq, etc).
  • Introduce new module FSharp.Core.Compat.*, which will contain a bunch of AutoOpen (?) modules, which will be shadowing a standard library modules and will have "default" corelib behaviour.
  • New compiler, when used with version < X (where X is the version this feature will be introduced), will auto-import FSharp.Core.Compat.

Approach has some pros and cons:

Pros:

  • New features might be used out-of-the box within new language version.
  • More granular control based on language version.
  • Ability to introduce perf/usability improvements which were blocked by backwards compatibility before.

Cons:

  • Behaviour will be (sometimes vastly) different when using different language versions/compiler, even when just upgrading SDK.
  • People who explicitly update FSharp.Core only or if FSharp.Core is loaded at runtime, might face breaking changes at runtime without any warnings.
  • Additional level of complications.

Question is do we think it's worth it? I personally do, since otherwise core library will be stagnating and lagging behind in features.

fyi @T-Gro @Happypig375 @dsyme @cartermp

Request for comments: additional approaches, flaws in current approach.

Update 1: For those with edit permissions, feel free to add to OP's pros/cons and additional approaches.

Update 2: We've had an idea of "layered" core library, which essentially may cover this approach as well, but ids probably a bit orthogonal to this specific discussion (thought related to it).

You must be logged in to vote
All reactions
  • 👍 29
  • 👀 1

Replies: 25 comments · 73 replies

Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

T-Gro
Nov 6, 2023
Maintainer

A few thoughts:

  • I would like to see the design for .Compat.* to be more of a family of assemblies, and not just 1. That way, pieces of it could be imported without taking the whole dependency as a whole
  • There should be 0 work needed for brand new projects, this should ideally be a hidden detail. Solvable either via dependency on lang version (as Vlad wrote above), or by modified project templates.
  • The following scenario should be considered: "I have an existing project. I update it to new NET+ new langversion. I do assume that if I do not see any new warnings/errors and the code compiles, I should not run into runtime issues".
    • This point is especially relevant for breaking changes which only happen on edge cases and do not have diagnostics, the whole nan equality semantics comes to mind.
    • I do not have an answer, but it is a problem area which should be kept in mind. Could custom code analyzers help here? A lot more aggressive "migration notes" about the changes which can exhibit such behavior?
You must be logged in to vote
All reactions
2 replies
@T-Gro
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

T-Gro Nov 6, 2023
Maintainer

For the runtime breaking scenarios, I think the "project template" solution might be a solution.
Existing projects unaffected, expert users can define it manually, new projects get the relevant setting by default via a template.

All reactions
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 6, 2023
Maintainer Author

A few thoughts:

  • I would like to see the design for .Compat.* to be more of a family of assemblies, and not just 1. That way, pieces of it could be imported without taking the whole dependency as a whole

How would it be distributed? We don't really want a new NuGet, or more "special" assemblies/projects at this point, it might become a maintenance nightmare very soon (since FSharp.Core is special and has features enabled only for it, and we will need to extend it for number of libraries).

Idea of "layered" core library is a good one, but probably a bit orthogonal to this one (thought related to it).

  • There should be 0 work needed for brand new projects, this should ideally be a hidden detail. Solvable either via dependency on lang version (as Vlad wrote above), or by modified project templates.

This might work, however, we should keep in mind that it will require bringing in some msbuild magic to when we invoke compiler, to let it know which libraries/modules/namespaces to autoload. Logic of "default" behaviour will be spread thin between project file, compiler and core library (parts). This will also become very polluted very soon, if we will chose this approach.

  • The following scenario should be considered: "I have an existing project. I update it to new NET+ new langversion. I do assume that if I do not see any new warnings/errors and the code compiles, I should not run into runtime issues".

    • This point is especially relevant for breaking changes which only happen on edge cases and do not have diagnostics, the whole nan equality semantics comes to mind.
    • I do not have an answer, but it is a problem area which should be kept in mind. Could custom code analyzers help here? A lot more aggressive "migration notes" about the changes which can exhibit such behavior?

It's also good to keep in mind that it's not how it currently works. Right now, one opts-in implicitly into new language features every time SDK is updated for the project (i.e. we always use latest language version), which inherently might bring some breaking changes (mostly, compile-time).

All reactions
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

isaacabraham
Nov 6, 2023

I think for so-called "hypothetical" breaking changes this seems to a reasonable way forward. It just needs to be very well documented and clear in the release notes for the language / FSharp.Core release version.

You must be logged in to vote
All reactions
  • 👍 2
5 replies
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 6, 2023
Maintainer Author

Problem is nobody reads those, and if they do, it shows that people don't pay enough attention.

All reactions
@mrange
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

mrange Nov 6, 2023

Understanding how release notes interacts with your own code base can be hard. I might read it and think this should be good we don't rely on this but then there is one part of our project that relies on it directly or perhaps transiently through external dependencies which we have no insights in.

Could still be the reasonable way forward. F# is rather small language and has a pretty up to date community so could still be worth it in a risk/reward scenario.

All reactions
@houstonhaynes
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

houstonhaynes Jan 5, 2024

Can an Intellisense refactor save us?

All reactions
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Jan 5, 2024
Maintainer Author

Can an Intellisense refactor save us?

You mean like showing potential braking changes to people?

It sure can, but I am not certain how to make them predictably detectable.

All reactions
  • 👀 1
@houstonhaynes
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

houstonhaynes Jan 5, 2024

Ah - that makes sense. I echoed Mark Seeman's missive about linters earlier today and I kinda felt sheepish about making this suggestion as a cheap way out.

All reactions
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

OnurGumus
Nov 6, 2023

Just 2 cents, please always keep an eye on Fable compatibility.

You must be logged in to vote
All reactions
3 replies
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 6, 2023
Maintainer Author

That was a point in recent discussion I had as well. Doesn't Fable have its own core library? Or do they have FSharp.Core fork and use it as a source?

I'd imagine they'll need to have a per-language mapping from FSharp.Core to target?

I imagine if we choose to do shadowing, no "binary" breaking changes will take place.

All reactions
@dbrattli
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

dbrattli Nov 6, 2023

Yes, Fable reimplements the core library for all languages. Any breaking change should eventually be a breaking change in Fable as well

All reactions
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 6, 2023
Maintainer Author

Yes, Fable reimplements the core library for all languages. Any breaking change should eventually be a breaking change in Fable as well

But only binary breaking changes, not runtime ones, correct? So, shadowing wouldn't affect anything.

All reactions
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

cartermp
Nov 6, 2023

otherwise core library will be stagnating and lagging behind in features

I know there's the equality issue, but what else?

Note that there's no guarantee of forwards-compatibility, and so for example, if FSharp.Core were made netstandard2.1 only, that would be fine. We broke off from older .NET Framework support in this way in the past. So I think it's worth separating issues that are unrelated from newer/older .NET versions from simply making changes to the core library.

You must be logged in to vote
All reactions
3 replies
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 6, 2023
Maintainer Author

I know there's the equality issue, but what else?

Vectorized collections operations, for example. Or if we'll want to migrate async to state machines, there might be some breaking changes.

All reactions
  • 👀 1
@mrange
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

mrange Nov 7, 2023

I remember a huge PR a few years ago by @manofstick to replace Seq with something significantly faster that I seem to remember wasn't accepted due to risk of regressions. That would have been great to get in! I am one the weird ones that have a hard time using Seq due to being aware of the slowness and memory consumption of it even when it doesn't matter.

All reactions
  • 👍 1
  • ❤️ 3
@EricM81
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

EricM81 Jan 5, 2024

The Option type being a ref type when it should be a value type is a breaking change needed by the high-perf, real-time devs. Having to use OptionValue is a PITA. Many libraries only accept/return Option<'T>, which generates a lot of GC activity in a hot loop.

All reactions
  • 👍 2
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

Happypig375
Nov 6, 2023
Collaborator

Good direction, a specific point I would like to see changed is that FSharp.Core.Compat.* should include a version, e.g. FSharp.Core.Compat.v8.* for compatibility with version 8 or below. Also, how about syntax changes? Like resolving fsharp/fslang-suggestions#472 in just one update, fixing meaning of (~~~).

You must be logged in to vote
All reactions
3 replies
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 6, 2023
Maintainer Author

Yeah, versions are up for discussion, not sure if it's the prettiest solution or not. In regards of syntax changes - also need to think more about it, what would it imply and how can we communicate this.

All reactions
@cartermp
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

cartermp Nov 6, 2023

I think it could get pretty unwieldy to maintain differing versions here :/

All reactions
  • 👍 1
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 6, 2023
Maintainer Author

I think it could get pretty unwieldy to maintain differing versions here :/

That's my concern as well. Also opens up potential rabbit hole of "try to figure out what implementation is used based on sdk, language version and project flags"

All reactions
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

Lanayx
Nov 6, 2023

My understanding is that if breaking change is actually fixing the long-standing buggy (or ineffective) behavior then it's normal. Relying on "incorrect" or undocumented behavior is a mistake anyway, so fixing it to to be "correct" only makes invisible bugs to become visible.
Another reason to just move forward is semantic versioning - major version change already implies some breaking changes, so it's not a big issue.
Therefore from my point of view introducing a FSharp.Core.Compat.* module seems like waste of time, which could be used for example for multitarget support in Core library (which might be another topic for discussion).

You must be logged in to vote
All reactions
  • 👍 7
  • ❤️ 1
9 replies
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 6, 2023
Maintainer Author

I see three cases

  1. I upgrade sdk and fsharp core (which is the most often) - the compat will be not used, so it won't help avoid issues

  2. I use old sdk and upgrade only fsharp core - compat will be not used, so it won't help avoid issues

  3. I upgrade sdk, but keep old fsharp core - compat will be used, so it will help avoid issues

It won't, just an old fsharp.core will be used, depending on language version.

I've ever used only 1 and 2 in my experience, option 3 where compat makes sense seems to be the least often used

That's the whole point, introduce a breaking change, with ability to quickly opt out (either via language version or some property). Alternatively it can fully be opt in via the same mechanism.

Can't just make a breaking change and not let people to chose to fallback to the old behaviour.

All reactions
@Lanayx
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

Lanayx Nov 6, 2023

with ability to quickly opt out

This doesn't have any importance to me. Let's say I have a silent bug sitting in my code which relies on buggy NaN comparison. It's 99% chance that I don't know about it (otherwise I would fix it myself). As I don't know about it - I upgrade FSharp.Core to get new features and/or performance (why do I upgrade otherwise). And it appears that upgrade breaks in production. At the moment when I investigate and issue, it would be clear that the issue is that I relied on NaN in the first place, so I'll rather fix it than enable compat.

Can't just make a breaking change and not let people to chose to fallback to the old behaviour.

If people are that afraid of doing upgrade that after upgrade they want to enable compat, then they shouldn't upgrade at all. Because the unintentional breaking changes can still happen and nobody is immune of those.

All reactions
  • 👍 1
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 6, 2023
Maintainer Author

This doesn't have any importance to me.

Exactly, you're only describing your experience, which is not necessary mapping to others'.

I talked to some of F# folks who use it in bioinformatics or telecoms, and they will appreciate if we don't break much of runtime.

All reactions
@Lanayx
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

Lanayx Nov 6, 2023

appreciate if we don't break much of runtime

I'll definitely appreciate this as well! :) But whenever I upgrade major version of any library or framework, I do acknowledge that runtime can be broken. Also, if I really care about the minimizing the impact of upgrade, I'll very carefully read release note and possible breaking changes of major version change.

All reactions
  • 👍 1
@charlesroddie
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

charlesroddie Jan 10, 2024

I agree with @Lanayx . Any runtime bugfix is "breaking" since when fixed it is a runtime change in behaviour and people may be relying on the bug. We should not be scared about bugfixes!

Any approach that unblocks F# progress is good, especially in an area like the canonical example here which both fixes a bug and improves performance. That includes the proposal here. However the proposal adds additional complexity in communication, and may add additional complexity in the compiler, which will then result in making it harder to make improvements in future. So I believe the best approach would be to do nothing except fix the bug. If anyone for historical reasons wants to preserve runtime behaviour then they can specify an FSharp.Core version and a dotnet sdk.

All reactions
  • ❤️ 1
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

ForNeVeR
Nov 6, 2023

Another solution that comes to mind is to have a new Core lib "layer", something like FSharp.Core.VNext, that would shadow the FSharp.Core modules and introduce new, better (in whatever sense we consider them improved) versions for Array, List, Async and whatever else we want to replace/update in Core.

A reference to FSharp.Core.VNext would be explicit. New project templates would depend on a new version of FSharp.Core.VNext, while older projects wouldn't automatically migrate when updating the SDK or a language version.

(Yeah, this approach has a bunch of downsides, and for now I'm proposing it for brainstorming purposes only.)

You must be logged in to vote
All reactions
5 replies
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 7, 2023
Maintainer Author

That sounds no different to just a nuget with some shadowing modules.

All reactions
@ForNeVeR
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

ForNeVeR Nov 7, 2023

Yep, that is.

All reactions
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 7, 2023
Maintainer Author

Ok, done, what should I include next?

https://www.nuget.org/packages/FSharp.Core.Faster
https://github.com/vzarytovskii/FSharp.Core.Faster

We can test things on this package version and iterate faster than normal FSharp.Core.

All reactions
  • 😄 1
  • 🎉 2
@NinoFloris
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

NinoFloris Nov 8, 2023

Nice! When shadowing you might run into #13535 though, as we created such a library internally as well

All reactions
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 8, 2023
Maintainer Author

Ok, done, what should I include next?

https://www.nuget.org/packages/FSharp.Core.Faster

https://github.com/vzarytovskii/FSharp.Core.Faster

We can test things on this package version and iterate faster than normal FSharp.Core.

Just to be clear, I unironically intend to use it as platform for experiments and benchmarks. I also might try and use it in our VS extension (since it's more or less independent) and maybe in future LSP layer, since it'll be a new project with small to no risks.

All reactions
  • 👍 1
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

Happypig375
Nov 8, 2023
Collaborator

Is it possible to make options, choices, etc structs and automatically insert glue code for conversion to reference types on interop with old libraries?

You must be logged in to vote
All reactions
  • 👍 2
2 replies
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 9, 2023
Maintainer Author

Been thinking about it as well. Will experiment with it.

All reactions
  • ❤️ 1
@Xyncgas
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

Xyncgas Nov 26, 2023

Don't forget tuples, basically they're wrappers, should've been ValueType

All reactions
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

goswinr
Nov 12, 2023

If such a breaking change is introduced in Fsharp.Core 9 would that mean any nuget package referencing Fsharp.Core 8 could misbehave in my app using Fsharp.Core 9 ?

You must be logged in to vote
All reactions
1 reply
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 12, 2023
Maintainer Author

If newer core is loaded, then yes (given its runtime change and nothing was inlined).

All reactions
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

varon
Nov 13, 2023

DESTROY ALL NON-VALUE OPTIONS

You must be logged in to vote
All reactions
  • 🚀 4
5 replies
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 13, 2023
Maintainer Author

That is a non-behavioural breaking change. It will result in huge compile-time and runtime breaking changes, and will likely never happen in such form. I can suggest shadowing for all functions used to work with value options.

All reactions
  • 👍 2
  • 😄 1
@varon
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

varon Nov 17, 2023

Shadowing is a great idea that solves the problem. Throw in a nice compiler warning too and we'd be good to go!

All reactions
@Xyncgas
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

Xyncgas Nov 28, 2023

If we don't break things other people will, it's part of growing up, (none ValueType wrappers) needs byebye

All reactions
  • ❤️ 1
@cartermp
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

cartermp Nov 28, 2023

Shadowing doesn't guard against code that uses reflection.

All reactions
  • 👍 1
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Jan 7, 2024
Maintainer Author

Shadowing doesn't guard against code that uses reflection.

Yeah, but reflection should then depend not only on the structure but also on the types of entities (structs vs ref types, etc), which is surely a "deep" implementation detail, and people should not do that (I know they will anyway).

All reactions
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

Xyncgas
Nov 26, 2023

Leave behind people who wants to use the old version, by introducing change inside next version of F# / .NET and telling people if upgraded to newest version there is breaking change, start documenting the behavior of old version and the new version of the language is different and how, and making these changes a moduler part of the language, so people who wants to use the old version of a specific part of the language can specify so (some how), other parts of the language can be using latest F# features
Similar to how McDonald's would handle a situation, for example Sausage McGriddle w/ egg is now containing penuts, we say the change on Fox5 channel, now we are starting to label all McMuffins and McGriddles with allergic information in a small line of text under, as we are serving people patties without peanuts upon request

But first introduce breaking change in a way that both the new version and the old version is equally obvious, so McGriddle w/peanut and McGriddle without peanut both are on the menu, now 1~2 yrs has passed we can have the older version available in non obvious way and eventually be making the decision whether to keep the old version around (.NET Framework is around still)

Meaning in visual studio have the option of { use ValueType for the fsharp core library } available in .NET 9 and turned on by default

Now if we are starting to have a bunch of flags in Visual Studio project property and one single { use latest } flags to hide them all that's off by default for the brave, the fsharp language can be agile enough, similar to Haskell

If fsharp is performant and elegant, that's a result everyone wants seeing

Sometimes you simply wanted to write quantum entanglement in classical computer in fsharp because it feels closer to doing math

You must be logged in to vote
All reactions
2 replies
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 28, 2023
Maintainer Author

Leave behind people who wants to use the old version, by introducing change inside next version of F# / .NET and telling people if upgraded to newest version there is breaking change, start documenting the behavior of old version and the new version of the language is different and how, and making these changes a moduler part of the language, so people who wants to use the old version of a specific part of the language can specify so (some how), other parts of the language can be using latest F# features

Yeah, this isn't happening any time soon. Especially if the change is "silent" or runtime.

All reactions
@Xyncgas
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

Xyncgas Nov 28, 2023

People building their project in .NET 6 is still going to use the old version, you can achieve the adaptive compatibility because people has to change their project to use the newer version of Fsharp, as we are moving on to .NET 9 if they use new version of course they get new features and breaking changes and what comes with it

All reactions

This comment was marked as off-topic.

Sign in to view
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 28, 2023
Maintainer Author

This has nothing to do breaking changes in core library.

All reactions

This comment was marked as off-topic.

Sign in to view
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Nov 28, 2023
Maintainer Author

This, again, has nothing to do breaking changes in core library.

All reactions
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

ScottArbeit
Jan 5, 2024

I love planned, intentional breaking changes. It means that things are progressing, and the truth is: people complain about it, but most of the time it takes an average mid-level programmer less than a day to make whatever changes need to be made for new versions of stuff. (I'm not as big of a fan of unintentional breaking changes, i.e. bugs.)

We're in a new world with LLM's and Copilot. If you imagine a compatibility helper process of some sort (one-time migration tool? integration with IDE's? a target in MSBuild? just thinking out loud...) that submits code to an LLM and allows us to automatically evaluate it to find things that could be breaking with a new version, it allows us to move much faster than we ever have before in terms of breaking changes.

I'm a huge fan of the expansion in the last many years of warnings and code fixes (💡), but those rely on the compiler to find issues. LLM's can do more and different things than pure compilation. Yes, as of January, 2024, this seems like a less reliable way than compilation, but I think of Wayne Gretzky's famous quote: "I skate to where the puck is going, not where it has been."

Looking ahead to 2025-2026, with improved, much less expensive GPT-4-level models, and then more advanced GPT-5-level models, incorporating LLM's into our compilation processes seems like a no-brainer. This - breaking changes in a new language version - is exactly the kind of thing that's a perfect use case for starting down that road. Taking advantage of these new tools lets us accelerate the changes we can make with our languages.

I'm going to be building LLM usage into the source control layer with Grace in as many ways as I can think of. Using them with large context windows is expensive today but will be much less expensive in a couple of years, and using them for this - a one-time(-ish) migration between language versions - is so much less expensive than loaded-cost-per-hour for a programmer.

You must be logged in to vote
All reactions
0 replies
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

brianrourkeboll
Jan 5, 2024

I think I would favor the simplest break possible: release a new major version of FSharp.Core, referenced by default by the new SDK. If someone has pinned an older version of FSharp.Core, use that version; otherwise, use the one that came with the SDK—as is currently done.

When the new SDK compiled an existing project, we could use target framework as a heuristic and emit some kind of warning if it was lower than the framework version in the SDK at release and the project did not have FSharp.Core pinned.

The warning would mention the incompatible runtime behavior in the new FSharp.Core version.

The user could dismiss the warning for such a project in two ways: if targeting an older framework version, say net8.0, they could update the target framework to the latest version at release (say, net9.0); or, if targeting something like netstandard2.1, they could pin FSharp.Core—which is recommended for libraries anyway—to either an older version or the new version, as desired.

Pros

When compiled with the new SDK:

  • New projects automatically get the new behavior.
  • Existing projects that already pin an old version of FSharp.Core will work without change.
  • Existing projects that use an older target framework and that do not pin an older version of FSharp.Core will get a warning: the user will be guided to either upgrade the target framework or pin FSharp.Core, depending on whether they want to opt into or out of the new behavior.
  • No confusing namespace/module duplications.
  • Purely FSharp.Core and .NET SDK/target framework-based; no need to think about language version (unless there's something I'm not thinking of that necessitates language version being involved).
  • The old behavior is gone forever (unless you reference an older version of FSharp.Core).

Cons

When compiled with the new SDK:

  • If someone chooses to pin the new (major) version of FSharp.Core in an existing project without reading the warning or the release notes, they may unknowingly encounter runtime breaking changes.
  • Referenced libraries depending on the behavior of older versions of FSharp.Core could behave differently when FSharp.Core was rolled forward to the new version by the host project, whether explicitly or implicitly. Just how bad this could be would depend on how many such libraries existed and exactly what behavior such libraries depended on: The older treatment of NaN? Different implementations of obj.Equals and IEquatable<_>.Equals or IComparable.CompareTo and IComparable<_>.CompareTo?
  • The old behavior is gone forever (unless you reference an older version of FSharp.Core).
You must be logged in to vote
All reactions
  • 👍 3
0 replies
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

Smaug123
Jan 7, 2024

(epistemic status: personal non-canonical prediction of my employer's preferences)


We are currently stuck with FSharp.Core v5 because of an unfortunate choice many years ago to standardise on pickling instead of serialising. Any upgrade of FSharp.Core is currently breaking for us due to the resulting inability to read in output that was pickled from older versions of FSharp.Core. I've been putting forth some effort over the last few years to unblock an upgrade by moving parts of the world to more stable output formats, but it's a very slow job.

So it would make things much easier for us if we could opt into new behaviour purely from the SDK or language version rather than FSharp.Core.


Given warning and the ability to opt out on a case-by-case basis, I expect we are generally happy to accept breaking changes as long as they are very clearly indicated in the release notes. Enough of us do read the release notes each cycle that we'd learn about it immediately. We already view SDK upgrades with deep suspicion (because NuGet is typically unusable until at least the .20x release due to performance regressions in the .10x releases), so we are already on high alert when we move to a new SDK and I'd expect us to catch issues pretty quickly.


Our development model is to keep versions of critical libraries in tight lock-step across all our in-house dependencies, so "silent breakage when a library was built and tested with a lower FSharp.Core and was consumed in a binary with a higher FSharp.Core" wouldn't arise for us as often as one might expect.


Developers aiming for maximum compatibility already have to test against many different versions of the runtime, and this is easy with dotnet test --framework=blah. Testing against multiple versions of FSharp.Core in a consumer is a faff, though: to update a NuGet package version on non-Windows platforms (as far as I know) can't be done in general except through hacky heuristics. (Perhaps there's a way with some extremely cursed MSBuild invocations.) If the behaviour of my library can change when a consumer upgrades FSharp.Core, it would be great to have some easy way for me to test this in advance.

You must be logged in to vote
All reactions
  • 👍 1
9 replies
@charlesroddie
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

charlesroddie Jan 10, 2024

Interesting problem. Seems manageable to migrate by defining a serializer that adds a new serialization method in addition to pickler and a deserializer that deserializes either, then deserialize and reserialize the data, then delete the pickler, then update fsharp.core.

We are doing well with cbor serialization, we find that manual serialization is great for making changes needing backwards compatibility including occasional tricky sequential changes like the one above, and that github copilot is great at writing serializer code so you might find that the process goes faster now than it did a few years ago.

All reactions
@Smaug123
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

Smaug123 Jan 10, 2024

Yeah, it's all solvable in principle, but the correct choice of serialisation format is different on a case-by-case basis - it's not the kind of thing one can trivially automate, or else you end up trying to reserialise a petabyte of floating point data as JSON :)

All reactions
@charlesroddie
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

charlesroddie Jan 10, 2024

That may be, but 1. it's possible to choose something which dominates pickler in terms of speed and size, and 2. JSON is heavily dominated in both dimensions so never the correct choice.

All reactions
@Smaug123
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

Smaug123 Feb 19, 2025

(By the way, another thing that appears to break FsPickler is DictImpl between 8.0.100 and 9.0.100; I just reverted an FSharp.Core upgrade that was intended to work around #18298. Again, we are performing a completely unsupported operation; just wanted to record somewhere for posterity because I sometimes need to recall these things months later.)

All reactions
@T-Gro
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

T-Gro Feb 19, 2025
Maintainer

@Smaug123: Could you please point me to a break in DictImpl? I was not aware of that one.

All reactions
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

WillEhrendreich
Jan 9, 2024

I honestly think it's worth it. Dealing with different versions of things is already a challenge, but so often that challenge seems to come with little real world benefit for the hassle cost. A breaking change moving forward is.. Well actually moving forward. It can suck, but as long as there is a bunch of support for the change, something like making analyzers that check for known breaking changes, and supply suggestions, or diagnostics or some sort of clue about whether or not it's going to happen or what to do about it, I think it's ultimately healthy to cull the crufty, to prune the dead branches, etc... It's a cost, but the reverse is a higher one, I think.

You must be logged in to vote
All reactions
1 reply
@T-Gro
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

T-Gro Jan 10, 2024
Maintainer

This a good approach.

Those are the good breaking changes - if their breakiness can be detected by analyzing the source code.
Where this does not work and those are the riskiest changes - break cannot be detected by looking at source code only, and it changes runtime behavior.

Perfect example is the generic equality and how it works with floating point nan.

All reactions
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

goswinr
Jan 10, 2024

Which changes are actually proposed @vzarytovskii ? Only avoiding boxing on equality ? Is there a list with reasons , impact and current workarounds?

You must be logged in to vote
All reactions
1 reply
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Jan 10, 2024
Maintainer Author

This is not proposal for any specific changes. It's is a discussion of possible approaches if library breaking changes make sense and we want to introduce them, how it should be done.

All reactions
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

goswinr
Jan 10, 2024

Don't do breaking changes! Here is why:

The news about braking changes is scary and confusing for new F# adopters. So bad for F# adoption
All Nuget packages that use FSharp.Core 1.0-8.0 are potentially broken when used with the new FSharp.Core 9.0
Microsoft is respected for it's "It still works" philisophy. Not for it's "move fast, break things" philosophy.
Beeing able to trust my old code and all nuget packages is worth more than automatic performance improvements.
I would much rather see a comprehensive guide of the possible performance improvements and maybe even have editor tooling to do them. (like generating IEquatable interfaces on structs). Combine it with FSharp.Core.Faster and promote it!

Or did I miss other braking changes that are not about performance ? Or can't be fixed with shadowing or additional code?

You must be logged in to vote
All reactions
  • 👎 2
  • 👀 1
0 replies
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

varon
Jan 12, 2024

Any arguments made against breaking compatibility are pretty much defeated by the fact that we've already successfully navigated possibly the biggest transition in the .NET Framework -> .NET Core move.

If we could break compatibility with complete disregard for legacy code, imagine what we could do:

  • Just make Option<T> a struct
  • Totally remove Async<T> in favour of Task<T>
  • Make all tuples structs/C# compatible by default.
  • Tie nullability directly into C#-style nullability.
  • Fix a lot of the syntactic warts from ML compatibility days
  • Plus the entire laundry list of suggestions above in this thread.

I would venture to suggest, that while keeping compatibility is a noble goal, in the sufficiently long term it's highly detrimental to the adoption of F#. Trying to explain to new developers how/why all this stuff exists that we shouldn't use and have to work around is really annoying.

if we do not actually do this, F# will continue to languish in its warty state, filled with thing that seemed like a good idea at the time.

Is it really is the intention of Microsoft to literally keep all of these problems around forever? that seems like an engineering approach that I and many others would fundamentally disagree with.

I really don't want to have to be dealing with these legacy elements still in 10 years time - we might as well try to get rid of them sooner rather than later.

You must be logged in to vote
All reactions
2 replies
@vzarytovskii
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

vzarytovskii Jan 12, 2024
Maintainer Author

Any arguments made against breaking compatibility are pretty much defeated by the fact that we've already successfully navigated possibly the biggest transition in the .NET Framework -> .NET Core move.

If we could break compatibility with complete disregard for legacy code, imagine what we could do:

What you're suggesting is pretty much "Hey, let's not think of users with legacy code bases, who just update SDK and want their code just work, and instead do all sorts of breaking changes, for reasons.". It's not right approach here. There are a lot of F# projects out there which can't account such breaking changes, we for sure need to think about those.

  • Just make Option<T> a struct

We can't "just" make it a struct, it will break all existing libraries which are using options and not rebuilt with latest FSharp.Core. If we ever to do that, it will need a bunch of work in compiler to account for that (pretty much make (v)option an intrinsic and deal with conversions during typechecking).

  • Totally remove Async<T> in favour of Task<T>

They are not the interchangeable, they have different approach, cold vs hot, implicit vs explicit cancellation, composable vs not, etc.

  • Make all tuples structs/C# compatible by default.

Not sure what that means, but probably less breaking than others.

  • Tie nullability directly into C#-style nullability.

Also not sure what that means.

  • Fix a lot of the syntactic warts from ML compatibility days

That was discussed many times, we wanted to hide a bunch of mlcompat under separate lexer/parser.

I would venture to suggest, that while keeping compatibility is a noble goal, in the sufficiently long term it's highly detrimental to the adoption of F#. Trying to explain to new developers how/why all this stuff exists that we shouldn't use and have to work around is really annoying.

if we do not actually do this, F# will continue to languish in its warty state, filled with thing that seemed like a good idea at the time.

Is it really is the intention of Microsoft to literally keep all of these problems around forever? that seems like an engineering approach that I and many others would fundamentally disagree with.

It's not, we're not literally keeping all of these problems around forever. This is exactly why this discussion is started.

I really don't want to have to be dealing with these legacy elements still in 10 years time - we might as well try to get rid of them sooner rather than later.

There are people with different projects and use-cases, you described yours - when you're fine if every major version language does breaking changes, both compile-time and runtime, and you're dealing with those. The other side of the coin is people who can't and won't deal with them, as it simply doesn't work for them.

Is is important to find a middle ground (which this discussion aims to do), and not just say "hey, let's just change things" or "hey, let's keep this issue foreve", as it's not productive.

All reactions
  • 👍 5
@cartermp
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

cartermp Jan 12, 2024

Any arguments made against breaking compatibility are pretty much defeated by the fact that we've already successfully navigated possibly the biggest transition in the .NET Framework -> .NET Core move.

Just to note that the large majority of .NET Core work was in maintaining compatibility with most of .NET Framework, and especially in ensuring most libraries can get ported over to .NET Standard fairly painlessly. Of course there were some big breaks, but the reason why most people have been able to move their code over is explicitly because of legacy compatibility.

All reactions
  • 👍 2
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

Happypig375
Jan 13, 2024
Collaborator

Can we incorporate some features of FSharpPlus e.g. NonEmptySeq? Maybe a NonEmptySeq can even be defined as a seq with const generic length constraint > 0. These can help ensure domain constraints.

They are related to breaking FSharp.Core because of fsprojects/FSharpPlus#480: groupBy should produce NonEmptySeq instead of just seq.

You must be logged in to vote
All reactions
0 replies
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

nicholas-peterson
Jun 7, 2024

It might be useful to consult the changelogs for ocaml. I know next to nothing about ocaml, but notice that every release seems to have a decent number of 'breaking changes'. I wonder how they prepare developers for breaking changes coming down the pipe (if at all?). I'd imagine others in the F# community might be more aware of the culture in ocaml, and could weigh in.

I think adding 'Compatibility' modules/ns in core is more confusing. New users have to do software archaeology to gauge when to use it, F# dev team has to test both versions, veteran users have to alter a bunch of files either way. You sort of double the space of possible problems.

We have a pretty savvy and active developer community, and I would guess most F# codebases are on average not terribly large (~1-20KLOC?). If something would give people concern around a breaking change, they could opt to not immediately upgrade their .net version.

Rigidly maintaining legacy functionality is a problem that very popular languages must contend with (C, C++, Java, etc). I think F# isn't quite at the stage where we have to be so wary of this yet. Most of the time when I mention F# to developers they either don't know what I'm talking about or assume it's some kind of Fortran.

So, I guess my stance on breaking changes can be best summarized by an old Rob Zombie song, "Dig through the ditches and burn through the witches and slam in the back of my compiler."

You must be logged in to vote
All reactions
  • 👍 1
0 replies
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

nkosi23
Jan 19, 2025

F#'s commitment to backward-compatibility is key to its credibility in my opinion, but at the same time it is really unfortunate when key features and/or undesirable behaviours are not addressed for the sake of keeping everything backward compatible.

My2cents are that as an end-user, I'd be totally happy to have a few breaking changes with major releases as long as I can see that the team remains conservative with this type of changes, and that the overall culture remains one of stability, and that F# does not become some sort of toy project where the team breaks things left and right because it doesn't care about old versions.

You must be logged in to vote
All reactions
  • 👍 1
0 replies
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

EricM81
Feb 19, 2025

Is F# growing or slowly dying? I love F#, but Option<T> is a roadblock to adoption. It creates too much GC pressure for real-time processing, and try as I might to use ValueOption<'T>, not even fsharp.core.collections supports it.

We all know these things need to be fixed:

  • Just make Option<T> a struct
  • Totally remove Async<T> in favour of Task<T>
  • Make all tuples structs/C# compatible by default.
  • Tie nullability directly into C#-style nullability.
  • Fix a lot of the syntactic warts from ML compatibility days
  • Plus the entire laundry list of suggestions above in this thread.

People who NEED this stuff are waiting for it. The existing codebases...breaking changes are painful, but those maintainers love F#, want it to grow, and will meet the challenge.

You must be logged in to vote
All reactions
  • 👎 2
7 replies
@Lanayx
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

Lanayx Feb 20, 2025

Here I meant the hot path which shouldn't be allocating on the heap at all to avoid any GC pauses (probably used the wrong term)

All reactions
  • 👍 1
@T-Gro
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

T-Gro Feb 20, 2025
Maintainer

The first three points of that list would be quite easy to implement in a fork-of-fsharp.
Doing that in a way that would not rip the library ecosystem in half (see also: Python, Scala) is the hard part.

All reactions
  • 👍 4
@smoothdeveloper
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

smoothdeveloper Feb 20, 2025

@EricM81, F# is only slowly dying from having passed the threshold of immortality, the slowness corresponds to that of the heat death of the universe and beyond, as it has been inscribed in the source of conscience itself.

For all your low level needs, you don't have to rely on FSharp.Core library, and you can contribute to the practices that makes F# suitable.

You can take the compiler frontend, and make a C backend for Fable, with all the things you mention, or look at the existing Rust backend.

For dotnet users, F# users enjoy productivity parties in high value codebases, that are all slowly dying at a slower rate than code which is not null safe, full of OO hierarchies, bloated with virtual dispatch, etc.

The community is welcoming the non breaking suggestions, and looking forward for your PRs 🙂.

I'd like a whole program optimization that elides option and allocations for closures, etc., as much as you do, but however this may or may not exist, the mighty CLR team is doing the ground breaking work so it is less of a concern, to 99.99% of F# users (I know, this is not so many of them).

For most existing F# users, I think compiler & tooling performance is above priority than beating rust at lower level code (C# isn't there, why expect F# to be?).

All reactions
  • 👍 2
  • 😄 2
@brianrourkeboll
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

brianrourkeboll Feb 22, 2025

For what it's worth, recent improvements in the JIT can already make option allocations disappear altogether in some situations. For example:

open BenchmarkDotNet.Attributes
open BenchmarkDotNet.Configs
open BenchmarkDotNet.Environments
open BenchmarkDotNet.Jobs
open BenchmarkDotNet.Running

[<MemoryDiagnoser(displayGenColumns=false)>]
[<HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")>]
type Benchmarks () =
    let x = Some 3

    [<Benchmark>]
    member _.DoSomeStuffWithOptions () =
        x
        |> Option.map ((+) 1)
        |> Option.map ((+) -1)
        |> Option.defaultValue 0

ignore <| BenchmarkRunner.Run<Benchmarks>
    (DefaultConfig.Instance
        .AddJob(Job.Default.AsBaseline().WithRuntime CoreRuntime.Core80)
        .AddJob(Job.Default.WithRuntime CoreRuntime.Core90))
| Method                 | Runtime  | Mean      | Ratio | Allocated | Alloc Ratio |
|----------------------- |--------- |----------:|------:|----------:|------------:|
| DoSomeStuffWithOptions | .NET 8.0 | 4.0503 ns |  1.01 |      48 B |        1.00 |
| DoSomeStuffWithOptions | .NET 9.0 | 0.4249 ns |  0.11 |         - |        0.00 |

It looks likely that we'll get more improvements along those lines in .NET 10. (Of course the JIT cannot solve everything, but it's not nothing.)

All reactions
  • 👍 3
@manofstick
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

manofstick Feb 22, 2025

This is great, but then if you are storing objects in objects or passing them to non-trivial functions this goodness disappears, and then you end up with a mix of ValueOption and Option and it's just not pretty.

Personally I would recommend to just use Option and not be worried about garbage. It's prettier and more supported.

Like most of the time it just does not matter. Machines are amazingly fast. If you do care about performance though, maybe don't write your code in fsharp (but really we're talking about the 1% cases here - most performance is going to be algorithmically driven, not low level details)

All reactions
  • 👍 1
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

EricM81
Feb 21, 2025

There are many reasons why Option should be a struct. There are zero reasons for it to be a ref. This mistake was made long ago, and the general apathy shown by the community to people affected by it has grown tiresome.

Option is a key component of the F# language. Have you tried programming without it?

OptionValue was added instead of fixing the issue. Have you tried using it?

For all your low level needs, you don't have to rely on FSharp.Core library

Have you tried not using FSharp.Core?

And especially never use fsharp.core.collections in critical path

Have you written something that outperforms a function in Collections?

C# isn't there, why expect F# to be?

C# is there. This is a basic thing.

Out of curiosity, what do you call "GC critical path"?

For me, it's real-time updates to geometry as a user clicks and drags control points. You avoid allocating anything because GC causes framerate stutters. You do the same thing you would do in C, and it's as fast as C. Use static arrays full of tiny, optimized structs to make the CPU cache happy, and you do as little branching as possible to make the CPU prefetch and branch predictor happy.

You must be logged in to vote
All reactions
  • 👍 1
  • 👎 2
11 replies
@charlesroddie
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

charlesroddie Feb 22, 2025

I'd be very surprised if collection module functions are slower than Linq esp for a type like FSharpList. Linq adds I believe two layers of overhead. It uses reflection doesn't it? Plus it doesn't know about FSharpList and will need to access it via seq and then convert back for something like map or filter. Benchmarks needed. Frankly if the collection module functions are slow then anyone can make a PR to make them faster.

That said, if you care about performance you don't want to be using FSharpList in particular except in rare situations. Out of hundreds of thousands of places in our codebase where collections are created I believe there is only one place where a linked list is useful for performance.

Option is a key component of the F# language. Have you tried programming without it?

Yes. We use it everywhere except optional arguments and ValueOption optional arguments are in preview. F# has made a lot of steady progress towards having ValueOption usable everywhere and allowing struct representations of things in general, so these comments are out of date. Also you created 2 new threads about Option when there was one already in this discussion @EricM81 .

All reactions
  • 👍 3
@Smaug123
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

Smaug123 Feb 22, 2025

I'd be very surprised if collection module functions are slower than Linq esp for a type like FSharpList. Linq adds I believe two layers of overhead. It uses reflection doesn't it? Plus it doesn't know about FSharpList and will need to access it via seq and then convert back for something like map or filter. Benchmarks needed. Frankly if the collection module functions are slow then anyone can make a PR to make them faster.

The most obvious places are where LINQ uses SIMD but FSharp.Core can't (because it's netstandard). I tried this years ago and failed for that reason. Orders of magnitude speedups are possible for things like Array.sum, but not in FSharp.Core.

All reactions
  • 👍 2
@charlesroddie
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

  • Uh oh!

    There was an error while loading. Please reload this page.

charlesroddie Feb 22, 2025

I'm curious about this work @Smaug123 . My understanding from playing around with Vector and with your code is that it applies only for a very short list of functions (like max, multiply, add, sin, etc.) on a very short list of types (including int, float, float32). It has an appalingly type-unsafe API. So I don't think it affects overall performance considerations of collection functions but is rather something specialist for a library which can be used explicitly, e.g. VectorOperations.max<'a when 'a is (int or float or float32?)>(span: ReadOnlySpan<'a>) (where the (int or float or float32?) resolves into either a type constraint which may be impossible to define, or multiple separate methods like maxFloat32). Perhaps can be added to Math.Net Numerics?

All reactions
@manofstick
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

manofstick Feb 22, 2025

I'd be very surprised if collection module functions are slower than Linq esp for a type like FSharpList.

Be surprised then, because they are. System.Linq wraps functions like Select (Seq.map) and Where ((Seq.filter) with an internal type so that when you have a combination you just combine the two function calls, rather than accessing through the MoveNext(), Current mechanisms. It also carries things like the Count of the collection through calls to Select so that the size is available for allocations to calls like ToArray, hence requiring no temporary buffers. Also When temporary buffers are needed, the first few elements are stored in a stack allocated object, which also removes the need for temporary buffers.

Now, if you're talking about List.map then yes, it would be faster than Select() |> List.ofSeq, but you are kind of talking apples an oranges here, as all the List functions are eager, and will allocate the complete list before moving to the next step in the pipeline as opposed to the lazy evaluation of Seq/System.Linq.

Linq adds I believe two layers of overhead. It uses reflection doesn't it?

It does not. Unless you are using IQueryable, in which case you are doing somethingn completely different. Linq-to-objects is the Seq equivalent. Linq-to-database uses runtime reflection/tree manipulation to do things such as converting to SQL.

Plus it doesn't know about FSharpList and will need to access it via seq and then convert back for something like map or filter. Benchmarks needed. Frankly if the collection module functions are slow then anyone can make a PR to make them faster.

Go for your life. I have done many in the past, you can probably hunt some of them down in other github comments that I have written. Although the changes in platform's code generation has changed results.

That said, if you care about performance you don't want to be using FSharpList in particular except in rare situations.

Meh, the reason to use FSharp is FSharpList. Really. The joy in FSharp is pattern matching with lists. Just don't let performance concern you. I mean for the vast majority of cases it just doesn't matter. And if it does matter, I would highly recommend not using FSharp. You just end up fighting the language. So go ahead an embrace list, along with all the garbage it creates.

Out of hundreds of thousands of places in our codebase where collections are created I believe there is only one place where a linked list is useful for performance.

You don't use linked-lists for performance - or if you do, you don't use an immutable linked-list for performance... (OK, I lie, there can be some edge cases - if you are leaning heavily into into sharing roots of the lists.)

Look, FSharp is great. I love the language, It taught me heaps, and my c# is better for it, and I'm a better mentor to other developers because of it. I highly recommend that people spend 6 months coding it exclusively so that you really get to appreciate the beauty. BUT, I would not recommend it for anything that I care about performance for. Yes you can carve that bits for performance out and do them in another language, or bastardize the usage of fsharp to make it work (I know, I have), but its just harder to then maintain. c# is till missing discriminated unions, but

abstract record Base { 
    private Base() {}
    public record A...) : Base(...);
    public record B(...) : Base(...);
}  

thing switch
{
    Base.A a => ...,
    Base.B b => ...
    _ => throw ...
}

does a faily good job, and presumably will be easy to refactor into whatever final form that they will eventually choose.

(c# pattern matching via switch statement also work fine, although ending each block with break just makes me sad)

All reactions
  • 👍 1
@Smaug123
Comment options
  • Uh oh!

    There was an error while loading. Please reload this page.

Smaug123 Feb 22, 2025

My understanding from playing around with Vector and with your code is that it applies only for a very short list of functions (like max, multiply, add, sin, etc.) on a very short list of types (including int, float, float32). It has an appalingly type-unsafe API. So I don't think it affects overall performance considerations of collection functions but is rather something specialist for a library which can be used explicitly...

LINQ just does it all behind the scenes, doing whatever type tests are required to ensure the dispatch is safe. You can use the low level Vector APIs, but you're expected just to let LINQ work out when it can speed things up.

All reactions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
💡
Ideas
Labels
None yet
26 participants
@vzarytovskii @smoothdeveloper @ForNeVeR @baronfel @dbrattli @goswinr @isaacabraham @varon @manofstick @ScottArbeit @mrange @Smaug123 @Lanayx @nicholas-peterson @OnurGumus @NinoFloris @EricM81 @cartermp @nkosi23 @houstonhaynes @brianrourkeboll and others
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.