Discussion
: FSharp.Core
breaking/incompatible changes
#16231
Replies: 22 comments 53 replies
-
A few thoughts:
|
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Just 2 cents, please always keep an eye on Fable compatibility. |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
Good direction, a specific point I would like to see changed is that |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Another solution that comes to mind is to have a new Core lib "layer", something like A reference to (Yeah, this approach has a bunch of downsides, and for now I'm proposing it for brainstorming purposes only.) |
Beta Was this translation helpful? Give feedback.
-
Is it possible to make options, choices, etc structs and automatically insert glue code for conversion to reference types on interop with old libraries? |
Beta Was this translation helpful? Give feedback.
-
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 ? |
Beta Was this translation helpful? Give feedback.
-
DESTROY ALL NON-VALUE OPTIONS |
Beta Was this translation helpful? Give feedback.
-
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 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 |
Beta Was this translation helpful? Give feedback.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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 ProsWhen compiled with the new SDK:
ConsWhen compiled with the new SDK:
|
Beta Was this translation helpful? Give feedback.
-
(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 |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Which changes are actually proposed @vzarytovskii ? Only avoiding boxing on equality ? Is there a list with reasons , impact and current workarounds? |
Beta Was this translation helpful? Give feedback.
-
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 Or did I miss other braking changes that are not about performance ? Or can't be fixed with shadowing or additional code? |
Beta Was this translation helpful? Give feedback.
-
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:
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. |
Beta Was this translation helpful? Give feedback.
-
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: |
Beta Was this translation helpful? Give feedback.
-
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." |
Beta Was this translation helpful? Give feedback.
-
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:
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:
FSharp.Core.Compat.*
, which will contain a bunch ofAutoOpen
(?) modules, which will be shadowing a standard library modules and will have "default" corelib behaviour.FSharp.Core.Compat
.Approach has some pros and cons:
Pros:
Cons:
FSharp.Core
only or ifFSharp.Core
is loaded at runtime, might face breaking changes at runtime without any warnings.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).
Beta Was this translation helpful? Give feedback.
All reactions