# Forward compatibility and stability of Julia vs. Packages

**URL:** <https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968>\
**Category:** General Usage\
**Created:** [April 25, 2023, 6:09pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968 "2023-04-25T18:09:37Z")\
**Posts on this page:** 20\
**Page:** 3

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [April 27, 2023, 12:24pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/41 "2023-04-27T12:24:42Z")

</div>

> [@kristoffer.carlsson](#):
>
> This is not a great idea for a few reasons:

You’ve just given three good reasons why packages _should not_ use Julia internals. All I’m saying is that if we claim that the ecosystem follows semantic versioning, then we should strongly encourage the ecosystem to _actually_ follow semantic versioning. I’m still surprised that this is a controversial idea.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 27, 2023, 12:36pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/42 "2023-04-27T12:36:34Z")

</div>

Well, if 1.9 is technically not released yet, and so isn’t “fair game” to look for packages breaking, I looked at a few popular packages under Julia 1.8.

I took the `General` registry as 2021-06-01 (just ~1 year before 1.8) and tried to install and precompile — not even test — a few of the most popular Julia packages with Julia 1.8.  
Lots didn’t work! Examples include `CUDA`, `FiniteDiff`, all of `SciML`, `LoopVectorization`, and even `IntervalSets`.

In my experience, this is the rule, not an exception — that lots of code (eg older package versions) transitively breaks when updating Julia. And this kind of breakage (doesn’t precompile and load) makes it impossible to do anything with those packages, not even a subset of their functionality.

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [April 27, 2023, 12:42pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/43 "2023-04-27T12:42:45Z")

</div>

> [@CameronBieganek](#):
>
> You’ve just given three good reasons why packages _should not_ use Julia internals.

This isn’t just about Julia internals. Your example:

```julia
[compat]
julia = "=1.6.7"

```

would also translate to packages using internals of other packages, right? Then x.y would be wrong vs. x.y.z? But I’m guessing we have a problem there too (common for the packages that actually do actually use internals? I.e. no problem in most packages, unless a few such packages are commonly used as direct or indirect dependencies…).

Doing that should be uncontroversial, and NOT stop Pkg and Julia/PkgEval(?), when its about packages and not Julia, as is.

> [@kristoffer.carlsson](#):
>
> It means we cannot even attempt to run PkgEval on the package for new Julia versions so if the package is somewhat popular we are in the dark about what effect a new Julia release will have on the ecosystem.

No? Currently yes, it would prevent, since you couldn’t install the packages for testing purposes. Should Pkg have some back-door to allow as if e.g. 1.6 stated despite 1.6.7 being in compat? Even if for just testing purposes, or just PkgEval? A counterpoint, if you have the back-door, people would start using it… But it would be great to have it even then, since then people would walk into the trouble with their eyes open, be asking for it. What is sensible for Julia PkgEval, if not many more users.

We want to “overhead” for users to discourage use of internals?

> [@kristoffer.carlsson](#):
>
> They themselves cannot even test the package on newer Julia versions

My plan would also work for them? I discovered test actually run with non-default opt (why I got unexpected (parallel) precompilation), to enable `--check-bounds=yes` so there’s a precedent to do some things differently for tests.

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [April 27, 2023, 12:48pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/44 "2023-04-27T12:48:48Z")

</div>

> [@aplavin](#):
>
> Lots didn’t work! Examples include `CUDA`, `FiniteDiff`, all of `SciML`, `LoopVectorization`, and even `IntervalSets`.

Example? I don’t know of any case that’s not working on v1.9. There are transitory periods where you cannot use an unreleased version of Julia because an autodiff package has not updated, and since a lot of SciML depends on autodiff packages you can be blocked from using nightly until that is fixed. However, I’d like to see what your test case is here since CI shows that “all of SciML” generally passes for any released Julia version?

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 27, 2023, 12:52pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/45 "2023-04-27T12:52:48Z")

</div>

Not sure if you read my message in full, because: I didn’t test on 1.9, and didn’t use the current state of packages. Both on purpose.

---

<div class="post-metadata">

**Author:** ![bkamins](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bkamins/32/208538_2.png) [@bkamins](https://discourse.julialang.org/u/bkamins)\
**Post date:** [April 27, 2023, 12:56pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/46 "2023-04-27T12:56:17Z")

</div>

Since DataFrames.jl was mentioned several times. The policy for this package is:

> Make sure to provide a release of DataFrames.jl that works on all promised released versions of Julia. Currently we promise that DataFrames.jl will work on Julia 1.6 or newer.

Note that if someone develops a project the specification of project environment is Julia version + package versions. So if someone updates Julia version of the project then also updating of package versions might be required.

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [April 27, 2023, 12:56pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/47 "2023-04-27T12:56:25Z")

</div>

On the topic of stability, I think the biggest issue right now is that we don’t have tooling for lower bounds. Semvar upper bounds are “fine”. There’s always going to be edge cases on the definition of breaking and public vs private etc. but I think that’s mostly handled other than the few edge cases. It’s lower bounds that’s the issue. When people dig up an old version, the version resolution part is scary is the fact that lower bounds are much less likely to be maintained.

So the most common way to break a good package is to set one of its dependencies far back, let it resolve, and try to use it. If any lower bound wasn’t correctly bumped, you can get a failure. These aren’t too difficult to fix, but IMO it means that we do need more tooling. I’m not sure what the best solution here is. I would for example be okay with something that auto-bumps all lower bounds to the version that was used on CI, or if we have enough compute actually test each package moving each dependency down to the lower bound. If we had this kind of testing then I think our ecosystem would be 10000% more stable.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 27, 2023, 12:58pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/48 "2023-04-27T12:58:31Z")

</div>

Totally agree! This is an orthogonal issue, and I tried to start a discussion on that some time ago on slack. Not much extra compute is needed to run these checks rarely, from time to time — and even this would catch lots of issues. What’s missing is infrastructure + buyin from packages.

I tried playing with this kind of compat testing for my packages, but buyin is critical — doesn’t make sense to do that for a small subset. See [oldnew\_compat.yml · GitHub](https://gist.github.com/aplavin/ec1024d00db6ebe8a7fa0a38aa8cca6c) for a github action example that tests for either oldest/newest versions of each dep, or for all versions from oldest to newest.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 27, 2023, 1:04pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/49 "2023-04-27T13:04:46Z")

</div>

> [@bkamins](#):
>
> So if someone updates Julia version of the project then also updating of package versions might be required.

Yes, this!  
Newer Julia versions often don’t work with old code =\> updating packages is potentially needed whenever updating Julia =\> updating Julia cannot be less painful/more comfortable than updating a package.

---

<div class="post-metadata">

**Author:** ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)\
**Post date:** [April 27, 2023, 1:32pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/50 "2023-04-27T13:32:15Z")

</div>

As a demonstration that the issue is lower bounds and not necessarily upper bounds, I created a test case of DataFrames resolution. I added packages at the minimums, but that wouldn’t resolve because some of the minimum package versions require an older Julia version than what’s allowed (oops), and then after slowly bumping I found a version that would install:

```julia
(@v1.9) pkg> add DataFrames Reexport@0.2.0 Missings TableTraits SortingAlgorithms@0.3.0 ShiftedArrays@1.0 IteratorInterfaceExtensions@0.1.1 DataAPI@1.14.0 InlineStrings@1.3.0 Unitful@1.0
   Resolving package versions...
   Installed InlineStrings ───── v1.3.0
   Installed SortingAlgorithms ─ v0.3.0
   Installed Unitful ─────────── v1.0.0
    Updating `~/.julia/environments/v1.9/Project.toml`
  [9a962f9c] + DataAPI v1.14.0
⌃ [842dd82b] + InlineStrings v1.3.0
⌃ [a2af1166] ↓ SortingAlgorithms v0.3.2 ⇒ v0.3.0
⌃ [1986cc42] + Unitful v1.0.0
    Updating `~/.julia/environments/v1.9/Manifest.toml`
  [187b0558] + ConstructionBase v1.5.1
⌃ [842dd82b] ↓ InlineStrings v1.4.0 ⇒ v1.3.0
⌃ [a2af1166] ↓ SortingAlgorithms v0.3.2 ⇒ v0.3.0
⌃ [1986cc42] + Unitful v1.0.0
        Info Packages marked with ⌃ have new versions available and may be upgradable.
Precompiling environment...
  ✗ SortingAlgorithms
  ✗ DataFrames
  2 dependencies successfully precompiled in 14 seconds. 36 already precompiled.
  2 dependencies errored. To see a full report either run `import Pkg; Pkg.precompile()` or load the packages
  1 dependency had warnings during precompilation:
┌ Unitful [1986cc42-f94f-5a68-af5c-568840ba703d]
│ WARNING: could not import FastMath.libm into Unitful

```

And boom that failed to precompile. This is not to single out @bkamins of course because you can find many examples of this around. For example, digging through Discourse search I found

> [@Failed to precompile DifferentialEquations and DiffEqFlux Julia 1.7.1](https://discourse.julialang.org/t/failed-to-precompile-differentialequations-and-diffeqflux-julia-1-7-1/82551/6):
>
> It probably forces OrdinaryDiffEq v5 via an old bound. It doesn’t need to though. @dhairyagandhi96 can we get that bumped?

where the issue was that someone added a non-updated Trebuchet.jl to their environment that burned it. This is then the interaction of two things:

1. Packages that haven’t updated in a long time are bad for the environment.
2. Lower bounds not auto-updating in any way means that a package that makes an environment generally old may install, but may be using a combination that has never been tested before and fails.

Of course the “simple” fix is to just remove the package that is old and bad. In that case, Trebuchet.jl either needed to be removed from one’s environment or updated, and in that case we did both and the world went back to normal. But, it does point to the fact that unmaintained repos are a “poison” that is hard to deal with.

So over time one of the things we have done with SciML, especially through 2022, is gobbling up some of these periphery libraries to keep them up-to-date and maintained because the real problems aren’t DifferentialEquations.jl but things like Trebuchet.jl or MomentClosure.jl that people install one time to follow a blog post and then never realize that their version environment is pretty messed up.

What can we do about this?

1. The aforementioned tooling on lower bounds. If lower bounds bumped correctly, you’d either get an older version of DifferentialEquations.jl that worked, or it would say it just cannot install. Honestly I’m fine with either, as a package resolution issue is a nice clear sign to the user that they have some mess to clean up and its their fault not something with the packages.
2. Some way of pointing out “old packages”. Even if it’s just a heuristic. The most common issue has an answer of “Yo, PackageX.jl last had a release during the JuliaCon v1.0 release event. You should expect that as long as this on your system that pretty much all other packages will not be good”. I’d prefer a very bold warning than the current silence, since this is the issue almost all of the time.
3. It would be nice to single out any package that isn’t allowing a recent version of a dependency. The new v1.8 package manager `^` is very helpful, but I’d like to know if there’s anything in my environment that has an unmerged CompatHelper PR. Anything that’s like that either should have an issue discussing why it’s not up to date, or is unmaintained. Either way, it’s a nice signal to the red flags in the ecosystem.
4. Periodic upper bounding of past releases. I would be interested at around the next LTS (v1.10?) to just make everything in current SciML not be allowed on v1.10, and just release new versions. Why? Over the last 5 years or so there have been some missed bounds. And what then tends to happen is that the cases where you may have missed a bound one time are the cases which have a loose bound. And then because that case has a loose bound, the package manager goes “wow this version is compatible with everything! Very nice!” and is very ready to pick that version over and over whenever you have one of the unmaintained packages in the system. So as a clean up measure, we need some way to clean those versions out so the package manager is in a cleaner slate to better resolve again.

Anyways, hopefully that distills what the core issues are. But in general this is pointing to missing lower bounds updates being some of the biggest issues, not upper bounds. We can debate the merits of SemVar, but that’s really small potatoes and is only exposing this other issue by making an unmaintained package cause resolution to the past, which then fails to work well because of untested lower bounds.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [April 27, 2023, 1:52pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/51 "2023-04-27T13:52:46Z")

</div>

> [@ChrisRackauckas](#):
>
> But, it does point to the fact that unmaintained repos are a “poison” that is hard to deal with.

My worry here is that I think CompatHelper is not enough. First because after some time it stops running, and second because not every package uses it.

IMHO an automated program that ran tests of packages with new dependency versions and suggested PRs is needed, independently of the CI setup of the packages.

Unmaintained repos will be more and more frequent, particularly in those cases where the author expects that nothing should be breaking the usage of the package given he/she has being careful in not using internals of other packages and provided appropriate compat entries. We need an external tool running tests to figure out these issues.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [April 27, 2023, 2:00pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/52 "2023-04-27T14:00:35Z")

</div>

> [@Palli](#):
>
> SemVer isn’t good enough. I sympathize with your view, for the average user, if something breaks for any (undocumented) reason at all (with an upgrade of Julia, or even any package), then it’s a problem (the solution, for now, not upgrade anything, including Julia, keep the Manifest), and the ideal would be it CAN’T happen (encapsulation).

> [@Palli](#):
>
> Are there any tools that check for use of internals? Could packages that do such be marked somehow? And such be made “friends” of other packages, in the C++ sense?

I’m a strong opponent of preventing access to internals entirely. We don’t have tools for checking whether something is internal or not is exactly what the discussion on github linked above is about:

> **[RFC: marking private and public APIs · JuliaLang julia · Discussion #48819](https://github.com/JuliaLang/julia/discussions/48819)**
>
> This is based on #42117 but is a bit different proposal that might be breaking for Julia 1.x based on different considerations thus I decided to open a separate issue to make reading a bit easier. ...

My position on this and on why we need that should be clear from that (I’m `Seelengrab` on Github.. I really should get on changing that name).

> [@aplavin](#):
>
> Well, if 1.9 is technically not released yet, and so isn’t “fair game” to look for packages breaking, I looked at a few popular packages under Julia 1.8.

There are release candidates, which are absolutely fair game to test stuff with. That’s exactly their purpose - catching potential breakage _before_ a version is released.

> [@ChrisRackauckas](#):
>
> On the topic of stability, I think the biggest issue right now is that we don’t have tooling for lower bounds.

This is attacking the symptom, not the cause. A failure due to “lower bounds” in an updated version can have one of three causes:

1. The package originally specified “too friendly” upper bounds, spanning more than one major (=_breaking_) version. The failure is fixable by setting a correct upper bound. The package may not install at all anymore, but that’s a MUCH better outcome than it just outright crashing with a `MethodError` or something like that.
2. A dependency of the package made a breaking change, but didn’t bump the major version.
3. The package relied on an internal object that was fair game to remove.

None of these is fixed by just running CI on old versions. You end up playing whack-a-mole and running after accidentally breaking changes. It’s better to plan for this from the start, by being able to programmatically declare what actually is API and then _never removing tests that test the declared API_ (unless you make a breaking change - then anything goes). Having such a capability would instantly solve 2 & 3, because it’s _obvious_ during development when you accidentally make a breaking change, as well as when you rely on an internal of a dependency.

The cause is us not having a way of defining what the API surface of a given thing actually _is_, in a way that is accessible programmatically. So let’s fix that.

---

<div class="post-metadata">

**Author:** ![bkamins](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bkamins/32/208538_2.png) [@bkamins](https://discourse.julialang.org/u/bkamins)\
**Post date:** [April 27, 2023, 2:02pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/53 "2023-04-27T14:02:38Z")

</div>

> [@ChrisRackauckas](#):
>
> I created a test case of DataFrames resolution.

Indeed, but it is a pure SortingAlgorithms.jl issue (i.e. this is not caused by a combination of packages issue):

```julia
(test) pkg> st
Status `C:\WORK\dev\test\Project.toml`
⌃ [a2af1166] SortingAlgorithms v0.3.0
Info Packages marked with ⌃ have new versions available and may be upgradable.

julia> versioninfo()
Julia Version 1.9.0-rc2
Commit 72aec423c2 (2023-04-01 10:41 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: 12 × 12th Gen Intel(R) Core(TM) i7-1250U
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-14.0.6 (ORCJIT, alderlake)
  Threads: 1 on 12 virtual cores

julia> using SortingAlgorithms
[Info: Precompiling SortingAlgorithms [a2af1166-a08f-5f64-846c-94a0d3cef48c]
ERROR: LoadError: UndefVarError: `Float` not defined

```

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [April 27, 2023, 2:25pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/54 "2023-04-27T14:25:44Z")

</div>

I think it’s worth taking a step back and noting that there are **four** different flavors of compatibility being discussed in this thread.

- Holding packages constant and changing Julia versions forwards (Julia’s forwards compatibility)
  - This is where we started — and what @aplavin wants to have work, with that concrete case of SortingAlgorithms _removing_ its prior dependency on internals. And this is where @CameronBieganek would put upper bounds to prevent the use of some old packages on newer Julias by rule. This is where we have PkgEval to help test new Julia releases and ensure the package ecosystem works with the new Julia version (yes, potentially by requiring new package versions).

- Holding packages constant and changing Julia versions backwards (Julia’s backwards compatibility)
  - This isn’t guaranteed — indeed packages may always choose to bound Julia versions so they can depend upon newer Julia features. Many package authors choose to support the most recent LTS+, but the desire to continue to support very old Julia versions was expressed in the original LTS thread before I split it by @stemann in [New Julia LTS? Are many using the current (Julia 1.6) LTS? - #10 by stemann](https://discourse.julialang.org/t/new-julia-lts-are-many-using-the-current-julia-1-6-lts/93919/10).

- Holding one package constant and changing another package forwards
  - These are the typical semver upper bounds that get put in `[compat]` — and these are indeed becoming more common thanks to CompatHelper.jl and this is the most common case in which it bumps things up. It’s much much less common for packages to do the equivalent of Julia’s PkgEval when releasing new versions, and this is where @Elrod is saying it can be more dicey than that first bullet.

- Holding one package constant and changing another package backwards
  - This is the case that @ChrisRackauckas describes with old Trebuchets and pinned DataFrames ecosystem. Just as with Julia’s backwards compatibility it’s not guaranteed to work by the terms of semver **but unlike Julia** folks are much less likely to think about the package versions that introduce new features and bound them because it’s not often tested. And it’s much harder to test (typically folks have a matrix of Julia versions in CI, this would require a matrix of package dependency versions). CompatHelper will help you get started here, but it won’t identify that some new code relied upon _newer_ dependency than you started with.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 27, 2023, 2:44pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/55 "2023-04-27T14:44:08Z")

</div>

Thanks for describing all these scenarios in details!

I think the last point,

> [@mbauman](#):
>
> Holding one package constant and changing another package backwards

is among the easiest to fix — at least it’s definitely possible, and a potential solution is readily apparent:

> [@aplavin](#):
>
> I tried playing with this kind of compat testing for my packages, but buyin is critical — doesn’t make sense to do that for a small subset. See [oldnew\_compat.yml · GitHub](https://gist.github.com/aplavin/ec1024d00db6ebe8a7fa0a38aa8cca6c) for a github action example that tests for either oldest/newest versions of each dep, or for all versions from oldest to newest.

Meanwhile, the first point

> [@mbauman](#):
>
> Holding packages constant and changing Julia versions forwards (Julia’s forwards compatibility)

is not clear if actionable at all. Maybe, the best what can be done is informing users that existing code/packages often breaks with newer Julia versions, they aren’t expected to work with exact same code as earlier ones.  
And, of course, try to depend on internals less — but this happens by necessity, I don’t think package authors deliberately dig into Julia internals.

> [@mbauman](#):
>
> yes, potentially by requiring new package versions

is not a solution, because this doesn’t keep old code but replaces it with updated one.

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [April 27, 2023, 2:55pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/56 "2023-04-27T14:55:12Z")

</div>

> [@aplavin](#):
>
> Maybe, the best what can be done is informing users that existing code/packages often breaks with newer Julia versions, they aren’t expected to work with exact same code as earlier ones.

Packages ARE expected to work with newer Julia, so I’m not sure if/how we would want to inform. Yes, that’s only if internals aren’t used, and while such packages may be infrequent, in practice it could affect many packages, or influential ones, because of dependency on such packages. So does that change the picture? Ideally no, since PkgEval should catch those and internals that would have caused breakage aren’t changed for final release (or RC). In some cases they are changed however… and considered allowed, by SemVer. Then some (few?) packages need to be updated that rely on those internals, but what does it mean for the dependants of those? If I understand, then they may not need updating (however it makes the Manifest idea broken, and thus e.g. Pluto, right..?).

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [April 27, 2023, 3:13pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/57 "2023-04-27T15:13:29Z")

</div>

> [@aplavin](#):
>
> > [@mbauman](#):
> >
> > yes, potentially by requiring new package versions
> 
> is not a solution, because this doesn’t keep old code but replaces it with updated one.

This is where I mildly disagree. New versions of compilers can and do expose downstream software bugs — bugs that require addressing for it to be used in the new version — and this can happen in any language ecosystem and can even happen if the downstream software isn’t explicitly “mucking about” in the compiler internals. A classic example is C’s undefined behaviors and how the newer versions of optimizing compilers are getting more and more aggressive at exploiting such cases. It can be easy to accidentally fall into undefined behavior that happens to work ok until a new version decides to punish you for it. Personally I find that to be far too punitive and user-hostile — just because the compiler team can say RTFM when downstream code breaks doesn’t mean that they don’t share in any of the responsibility to help prevent such things from happening.

The polar opposite approach to compatibility (never break anything in downstream code) leads you to never changing anything — as it’s possible for folks to depend upon buggy compiler behaviors and correct for them in their code in a manner that would break upon the original issue’s fix.

That’s precisely why I really appreciate all the work that goes into PkgEval. There’s a shared responsibility here. SemVer isn’t a legal contract; it’s a best-effort sort of way of communicating among ourselves about how software changes.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [April 27, 2023, 3:21pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/58 "2023-04-27T15:21:09Z")

</div>

Note that I never promoted

> [@mbauman](#):
>
> “never break anything in downstream code”

approach. What I propose is better informing users, that despite PkgEval (which is a great project!) newer Julia versions regularly break old code (eg old packages) in practice. It’s not an objectively bad thing, it’s just something to keep in mind.

Speaking from my own experience getting familiar with Julia, I really thought that this kind of breakage should be extremely rare or minimal, given how PkgEval is taken seriously. While in practice, lots of popular packages require updates to get them working on newer Julia versions. It’s nice to let potential users know about this (somewhere).

And looking at this very thread, somehow I even have troubles convincing (some) others, that this breakage actually happens (:

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [April 27, 2023, 4:08pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/59 "2023-04-27T16:08:45Z")

</div>

Sorry, didn’t mean for those quotes to put words in anyone’s mouth here (I was thinking of Linux and [Rich Hickey](https://www.youtube.com/watch?v=oyLBGkS5ICk) when I wrote them; Rich’s talk is quite good as usual and worth a watch) — I’ve removed them.

I think there are two or three things getting lost in the fray here:

- Julia updates don’t break typical user and package code — and we have pretty high confidence in this from running PkgEval.
- A number of packages do the extraordinary and are themselves quite atypical. Things like GPU compilation, differentiation, compiler-adjacent optimizations like LoopVectorization, etc, etc. And these extraordinary things often sit towards the bottom of the dependency trees. The number here is pretty small, but a good number of upstream packages do depend upon them, so if they break it has a larger splash radius and appears worse than it is. For example, the oldest version of IntervalSets (v0.2.1 from 2018) **does work** on Julia v1.8 and passes all its functional\* tests if allowed to use newer versions of its dependencies.
- These packages — the latest versions of them — _aren’t broken_ when a new version of Julia is released. But, yes, older versions might be.

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [April 27, 2023, 4:29pm UTC](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968/60 "2023-04-27T16:29:57Z")

</div>

Breakage in software (even with use of internals, it seems) can be avoided.

What does (forward) compatibility/“breakage” in software mean, and dependency on “internals” (not official API)?

Breakage can only mean (unintended bugs or) deleting stuff (Rick Hickey has a very good talk on this, and why SemVer not needed). Note, changing stuff, e.g. names, is basically deleting then adding [new name].

And it can only mean deleting from an API, or internals (neither is good), something you can call, or deleting (or accessing/mutating) a member of some struct or class.

If it’s a simple name change, maybe you had a typo, then it seems wanted, in theory you can keep both names, deprecate the older. When do you really need to drop the deprecated name? Never really, Rick argues, at least in open source code (if you want to respect your users). Before publishing, registering for us (or for non-public code [of a company]), you can justify it.

Another “need” is accessing a member of a class, or struct. Julia has no syntax to allow for aliases of member names (but it would be plausible in a language). I’ll concentrate on a different part of it, should you be accessing it somehow, or even know the name?

Since I mentioned encapsulation before (the proposed right way according to Parnas’ principles, having internals you don’t need, or shouldn’t know, or change directly), there’s an alternative:

> How do you achieve encapsulation with Clojure? [Clojure - Frequently Asked Questions](https://clojure.org/guides/faq#encapsulation)

> Because of its focus on immutable data, there is generally not a high value placed on data encapsulation. Because data is immutable, there is no need to worry about someone else modifying a value. Likewise, because Clojure data is designed to be manipulated directly, there is significant value in providing direct access to data, rather than wrapping it in APIs.

So APIs, “internals”, and mutability are considered harmful(?).

I’ll make this concrete with two examples (this one then my own):

> **[Parnas’ Principles](https://dwayne-phillips.medium.com/parnas-principles-448f919bcae6)**
>
> by Dwayne Phillips

> [@Elrod](#):
>
> Meanwhile, in the open source ecosystem, breakages are routine.  
> For example, today we have a new [patch version of StatsBase](https://github.com/JuliaStats/StatsBase.jl/releases/tag/v0.33.22) which breaks [KernelDensity.jl’s precompilation](https://github.com/JuliaStats/KernelDensity.jl/issues/113), which has [271 open source dependents](https://juliahub.com/ui/Packages/KernelDensity/4QyGx/0.6.5?page=2) that now all fail to precompile and load.
> 
> This is a regular occurrence among packages, but they can thankfully be updated quickly.  
> Still, you basically have to pin most packages if you have a lot of dependencies and want a stable experience.  
> […]  
> (FWIW, this example is because of one package relying on another’s internals gratuitously. But packages do that a lot.)

Was it “internals”? I didn’t look to carefully at this, it seems like a red herring if it was internals, the real reason is the “name” change.

To make this concrete, why (did @kristoffer.carlsson) delete aliases?

> <https://github.com/JuliaStats/StatsBase.jl/pull/840>
>
> As the comment in \`common.jl\` says, these are obsolete now with the terse covari…ant syntax.
> 
> Interestingly, there is apparently a whole package just for these type alises: https://github.com/AP6YC/NumericalTypeAliases.jl/blob/develop/README.md#aliases.

It meant another package had to make the corresponding change:

> <https://github.com/JuliaStats/KernelDensity.jl/commit/a7455913718f4aca1d7ee795992078c89d0f243b>

For my own example, rand() used to mean MersenneTwister, and I proposed a new (faster) RNG, and thankfully it was implemented in 1.7. The “API”, the name, is the same, but the stream of numbers isn’t (nor was it promized). It would have been very annoying to add rand2() for it, and make it an opt-in. The older RNG is still available, but an opt-out (and might go away in 2.0, possibly sooner if no one cares, I’m not sure many actually use it since all got migrated to the new one, and still it would be somewhat wrong to drop it).

[The new RNG was proposed because it’s faster, not because the old one was insecure, it could have been, then it would have been (even more) justified to do the change.]

[Previous page](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968.md?page=2)

[Next page](https://discourse.julialang.org/t/forward-compatibility-and-stability-of-julia-vs-packages/97968.md?page=4)
