Learning from the "No /= in Eq" debate

As I’m sure many of you know, the CLC has voted to remove /= from the Eq typeclass (Proposal: Remove method (/=) from class Eq · Issue #3 · haskell/core-libraries-committee · GitHub) in a relatively short timeframe (15 days).
I’m not here to debate the technical merit of the proposal per se, which are well articulated on the github thread, but I’d rather try to find a teachable moment that can guide the CLC decisions going forward.

The vote has caused quite a bit of debate (to put it mildly), from which a few main lines of reasoning emerged (well summarised by @isovector in a follow-up post : Dragging Haskell Kicking and Screaming into the Century of the Fruitbat :: Reasonably Polymorphic ) :

  • too quick, lacking broader community support
  • documentation becoming obsolete, especially the Report and textbooks currently in print
  • breaks backward compatibility
  • adds undue maintenance burden

From this, many have wondered what will be the broader impact to industrial adoption, that there seems to be no overarching vision to these point changes, the psychological and economical impact of maintenance busywork, and so on. Indeed, the whole hoopla seems to be indicative of a deeper dissatisfaction with how GHC Haskell changes over time rather than with the proposal per se.

It’s worth mentioning that this is still just an accepted proposal and an actual migration timeline is currently in the works (cc @nomeata ).

So: we’re all here to make this weird little language better while being productive with it, but the community is large enough that logical air-tightness of the typeclass hierarchy is not a sufficient reason for disruptive change.

4 Likes

For the brave, there’s also a recent reddit pile-up on the matter : https://www.reddit.com/r/haskell/comments/qsjwhh/dragging_haskell_kicking_and_screaming_into_the/ . I’d like to isolate a few interesting points from that too :

2 Likes

Something I have witnessed in these debates in the past is that both sides will always claim that their viewpoint is better for “industrial adoption” (just as they will for “new user experience” etc).

In this case, many have argued in the past that an unwillingness to make changes has hindered adoption because too many “warts” have been unable to be fixed, due to inertia. Similarly, many now argue that changing this “breaks stuff” and therefore deters industrial adoption. So, if one even accepts this as the right criteria, who is correct? Honestly, I don’t know. I think it is case by case, and in the absence of difficult to procure empirical evidence that is carefully interpreted and not taken as over-representative, a matter of judgement and guesswork.

My own personal guesswork is that in the scheme of things, this is a very minor change and doesn’t matter a lot either way at all! It is just another flashpoint between two general mindsets and approaches on the question, which presume industrial users with different sets of concerns and values – but both sorts of industrial users exist! (There is no “ideal” archetype here). So choices need to balance between all these concerns based on individual cases and the tradeoffs therein.

This change is so minor from a maintenance standpoint compared to many other changes, but also relatively minor in benefit that there’s really not much balancing to be done. I don’t think we can infer anything particular about the disposition of the CLC from this change, nor can we infer anything about future direction of priorities, nor does such a change either way set future priorities.

So my conclusion is that there is a time and place to get very worked up about big picture questions about language evolution, but this change is not it. It is so small in the scheme of things that it is just a rorschach blot onto what everyone projects their hopes and fears about other, larger but unspecified changes.

For what its worth, I would encourge people to save their ammunition and stridency for when we have something bigger to yell at one another regarding.

12 Likes

The phrase “the last straw” comes to mind. Maintainers and business users are at their wits end with churn as it is.

The fact that people upstream are surprised and apparently mystified by this reaction shows how out of touch they are.

I’ve said this elsewhere, but many good Haskellers I know and businesses became tired of having this discussion and just switched to Rust/others for good.

5 Likes

Rust is a great language. It offers a lot of features Haskell doesn’t in terms of the design space it hits, the programs it makes it easiest to write, etc. We’ve seen people move from Haskell to Rust, and from some degree from Rust to Haskell, or to use both when the situation calls for it, etc. I’m really happy people are finding it appealing and there’s a lot to learn from it, and a lot of shared interests between the two language communities. I expect we’ll see plenty of hopping between languages with different sweet spots and environments, etc in the years to come, and this is all to the good. And there will be plenty of reasons for that hopping around. Personally, I doubt modest prelude changes will be a driving force behind much of it, but lets just call that an informed guess.

4 Likes

Ahem… rust has been incredibly backwards incompatible from my experience. Many projects rely on nightly features and compiling projects from 2019 is already a journey of installing nightly compilers by date and trying to figure out which one works.

4 Likes

I could never describe myself as upstream, differently streamed perhaps, but yes, I am mystified and surprised by the community reaction.

Having actively followed the /= proposal and commentary in real time, I would have described the CLC and the process as very in-touch. I read an open and balanced discussion covering all the issues that have gotten airtime since the decision. I would have described the 5 out of 6 votes as uncontraversial, given a context of ongoing (and breaking) development and planning around core libraries. I would describe this view as popular. The estimation and extent of breakage and management of this seemed serious and considered.

The CLC is clearly working within a mandate and understanding that breaking changes are acceptable to the community, subject to cost and benefit tradeoffs that require ongoing judgement, by them.

For me, the lesson being learned is that we need lots of different people with the broadest possible viewpoints to write down our core principles, and find common ground, such as the work being done in

Participation would help with the demystification process involved in trying to understand everyone’s viewpoint and care.

8 Likes

I came to say this, but could not summarize it so eloquently.

I’ve mostly been an observer watching this all go down over the last 5 - 10 years. As someone who wants to use Haskell to get things done, it’s pretty demoralizing to see the attrition in action.

Haskell has something special, so it attracts great people wanting to do great things, but the structure of our ecosystem, the disjointed community, and the out-of-balance power dynamics create unbelievable attrition.

It’s as if we’re bleeding out, and don’t recognize that is happening.

We could do something about this before it’s too late.

I don’t think this is a conflict between folks who don’t want any change and folks who want to address minor quibbles, and I don’t think the triviality of this change is reason to ignore frustrations. On the contrary, the fact that the change here is so minor is rather the point: we really need help with backwards compatibility. That a change like this requires package maintainer action is, I think, a core part of the frustration. Yes, there are some who truly don’t want any change, but there is also a group who would shrug and move on if their old code automatically worked with this new arrangement.

How could it do so? One idea is something like, if both methods are defined, ignore (/=) and emit a future compat warning. If only (/=) is defined, generate (==) and emit a future compat warning. One can imagine a tool for producing diffs that effect such changes so that maintainers can migrate with minimal effort when doing other maintenance.

One can see this episode as a call for prioritizing efforts to help with the update treadmill.

7 Likes

Definitely! Although I don’t think some complex magic in the compiler is advisable here. Instead (and as discussed inbte the proposal discussions) it may now be the right time to build towards automatic code updating tools. They don’t need to be perfect and handle every possible change in every code base, but 80% would already be a game changer - once we have the infrastructure for such a cabal fix-code command.

So maybe the triviality of the (not often required) change is a good incentive for someone to create that framework and integration? And once we have it, we, as a community, can become even more relaxed about small improvements…

4 Likes

Could someone give some examples of similar tools in other languages? I’m only aware of 2to3 for Python.

Indeed! In the twitter thread I proposed using TH for this, as one can almost imagine the ability for the compiler to find migration TH to apply to code to update it and a standalone runner that can apply TH edits to source on disk.

scalafix was suggested as a point of comparison on twitter.

One example I know of, and greatly benefited from, was with Terraform. The v0.11 of Terraform used HCLv1, and v0.12 included significant changes to the syntax with the adoption of HCLv2.

As part of the migration, the Terraform team provided us with a command to automate updating v0.11 code to the v0.12 syntax - Command: 0.12upgrade - Terraform by HashiCorp.

To do the upgrade by hand would have been atrocious, but the command made it manageable.

That said, I don’t think such a tool would significantly change the experience with Haskell. Even if we had the tooling in both stack and cabal, that doesn’t address the way our ecosystem is organized and structured, it doesn’t address the disconnect between policy and vision at GHCHQ, nor how we use PVP, nor the lack of a “standard library” with strong guarantees.

This situation isn’t as simple as we might like to believe, and it’s just a symptom of the deeply entrenched problems in the Haskell community.

1 Like

I don’t understand all the maintainer panic. It’s not like (/=) will go away at 3am yesterday.

We may have years ahead to get such warts removed if we need it. Stick a forward-compatible change in one of your releases during 2022 and move on. By 2023 we’ll have a language with one less thing to worry about. Ditto for other deprecations.

Haskell is touted as “easy to refactor” at every corner. And it is not far from truth. Let’s use the powers we preach.

(I do maintain a bunch of projects since GHC 7.0 and I’m fine with it.)

7 Likes

I can relate to all of this, it matches my experience. Of course anything can be fixed and evolved with a sufficient amount of elbow grease but I’m afraid there was nothing “technical” about this whole uproar.

In fact I started this thread to understand those more social and psychological aspects of resistance to change, or perceived lack of “control” over a shared common resource [1] such as base.

I find it extremely indicative that very experienced users of the language have diametrically opposite views on this situation (see few comments above by @sclv and @chrisdone : “it’s a small change, nbd” / "you are f*ing up, people are rage-quitting").

Of course you can’t control what people say on social media but a few loud and authoritative voices sent shockwaves through the Haskell echo chamber, which was demoralizing. I was particularly disappointed with L. Augustsson, G. Hutton and E. Meijer, founding figures in Haskell in their own right, who didn’t involve themselves with the discussion process but only showed up on twitter to complain and shame in a very non-constructive way.

It’s high time we stop talking past each other since we all draw at the same well at the end of the day.

[1] E. Ostrom, Governing the Commons, Cambridge

2 Likes

Also, the Github discussion on guidelines for breaking changes shared by @tonyday567 is a good thing. We should all provide perspective and actionable insight to the CLC over there. They are doing a hell of a job in keeping the ship steady.

1 Like

Time for a little historical context

3.7 Haskell and Haskell 98

The goal of using Haskell for research demands evolution, while using the language for teaching and applications requires stability. At the beginning, the emphasis was firmly on evolution. The preface of every version of the Haskell Report states: “The committee hopes that Haskell can serve as a basis for future research in language design. We hope that extensions or variants of the language may appear, incorporating experimental features.”

However, as Haskell started to become popular, we started to get complaints about changes in the language, and questions about what our plans were. “I want to write a book about Haskell, but I can’t do that if the language keeps changing” is a typical, and fully justified, example.

Many of the criticisms leveled at this change seem to belong in this category: “I can’t teach/use Haskell with confidence if it’s constantly changing …” - however:

We made no attempt to discourage variants of Haskell other than Haskell 98; on the contrary, we explicitly encouraged the further development of the language. The nomenclature encourages the idea that “Haskell 98” is a stable variant of the language, while its free-spirited children are free to term themselves “Haskell.”

So by this observation:

  • that (/=) was a method of Eq on 2021-11-11
  • then was just another function on 2021-11-12

means Haskell [sans version] is proceeding generally as expected:

the Haskell community […] usually not only absorbs language changes but positively welcomes them: it’s like throwing red meat to hyenas.

…but judging by a few of the comments here, some of those hyenas are now begging for Alka-Seltzer(R). There was an attempt to bring some order to this chaos by providing another stable version of the language:
https://mail.haskell.org/pipermail/haskell-prime/2016-April/004050.html
which eventually went nowhere:
https://reasonablypolymorphic.com/blog/haskell202x

…and here we all are today, trying in our own ways to find the balance between evolution and stability. Here’s an idea: let’s call the language & version supported by e.g. GHC v. 9.2.2 “Haskell 1.9.2.2”, based on the following observation:

  • early versions of Haskell: v. 1.0 to 1.4
  • Haskell 98: v. 1.5
  • Haskell 98 with addenda: v. 1.52
  • Haskell 2010: v 1.6
  • Haskell 2010 with GHC-isms: v. 1.7.0.1

…and so forth (I’m conveniently ignoring the advent of “Dependent Haskell”: maybe a whole new language, like how Raku appeared? :-). If it’s possible, doing this officially could help to ease the tension between stability and evolution:

  • Academics (and perhaps builders of alternate Haskell implementations) can choose the specific version of Haskell that interests them;

  • The removers of “warts and moles” from Haskell don’t have to wait [indefinitely?] for e.g. “Haskell 2024” to have those changes accepted: they just need to increment the current Haskell version and update the Haskell Report accordingly, along with any other pertinent documentation.

One other possible benefit:

  • Updating said documentation can also be made a requirement for adding new language features.

Of course, you still can add or remove all the features you like to your own private copy of GHC, but your changes will only be given any serious consideration if you’ve documented it well (e.g. as a draft revision of official documentation). Not only does this focus the attention of those proposing potentially-breaking changes, it could allow more of them to be aggregated together in each new Haskell version - occasional large changes instead of frequent small ones.

Alternately, just adopt the useful parts of the approach the Rust language relies on to strike it’s balance between stability and evolution, and adapt them according.

But all this talk of decisions in hindsight regarding head and tail being in the Haskell prelude or (/=) being a method of Eq may be missing the point - in hindsight (again!), perhaps the better decision was for the original Haskell committee to design two non-strict functional languages instead of one:

  • a “concept language” intended for evolution, which charges ahead with new concepts and ideas;

  • a “production language” intended for stability, which follows behind cautiously at a safe distance and picking up only the most enduring and useful of features.

Was this a setup for one of those “Haskellers would literally fork the compiler instead of going to therapy” jokes?

I like “we all draw at the same well”. We have different priorities and different goals. We have different cost models: what is a minor inconvenience for one person is a huge pain for another. And yet we share a common language and a common ecosystem, one that we all care about and all want to succeed.

If we yell at each other, we aren’t going to make that commons better. We have a better chance if we (somewhat dispassionately) share our varying priorities and cost models. It’s not a silver bullet – some goals are truly incompatible – but it’s the best shot we have.

Is the book you cite a good one? This one, right?

6 Likes