From b9ae37aab991711f92ad63724a3ac39596282259 Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Wed, 26 Nov 2025 17:46:43 +0100 Subject: [PATCH 01/21] Add blogpost on udnerstadning breakage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚱🚱🚱 --- .../investigating-lang-ext-semantics/index.md | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 content/investigating-lang-ext-semantics/index.md diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md new file mode 100644 index 0000000..c02f2d8 --- /dev/null +++ b/content/investigating-lang-ext-semantics/index.md @@ -0,0 +1,202 @@ +We analyzed the head.hackage patches to understand +why code breaks on new GHC releases. +Surprisingly, most breakage wasn’t caused by +Template Haskell—it came from deeper semantic changes in language extensions. +This post walks through the main categories of breakage, +why they happened, and what they tell us about long-term stability. +If you care about a smoother upgrade path for Haskell users, +we invite you to participate in the Stability Working Group. + +The [Haskell Foundation Stability Working Group](https://blog.haskell.org/stability-working-group/) +is interested in understanding +why breakage occurs. +This is an extension of our initial [investigation](https://jappie.me/analyzing-haskell-stability.html). +We've recently done further analysis on [head.hackage](https://ghc.gitlab.haskell.org/head.hackage/), +and learned surprisingly enough that the root cause +of a lot of breakage isn't Template Haskell, +but seems to be from language extension semantics[^meaning]. +We're doing this investigation to better understand where the Haskell +Foundation stability working group should focus it's efforts. +About a year ago I started on analyzing the causes +for all `head.hackage` patches, but I got bogged down +by the sheer amount of work. +Trevis suggested focusing on the +most important question, +why do language extension semantics cause so much breakage? +So instead of being bogged down by analyzing all +the patches, I just looked at the 12 patches from language extension +semantics. + +[^meaning]: The precise meaning of features enabled by language extensions. I guess parser changes also count. + +This gave us the following table: + +| Name | Cause | Had warnings? | +|-------------------------------------+----------------------------------------------+---------------| +| Cabal-2.4.1.0.patch | simplified subsumption | no | +| Cabal-3.0.2.0.patch | simplified subsumption | no | +| Cabal-3.2.1.0.patch | simplified subsumption | no | +| data-r-tree-0.6.0.patch | parser change (see 1) | no | +| drinkery-0.4.patch | simplified subsumption | no | +| ghc-lib-parser-9.8.1.20231121.patch | rename forall identifiers (2) | yes | +| hgeometry-ipe-0.13.patch | Instances moved due to splice enforcement | no | +| singletons-3.0.2.patch | add TypeAbstractions as a language extension | yes | +| singletons-base-3.1.1.patch | add TypeAbstractions as a language extension | yes | +| vector-space-0.16.patch | * is type (4) | yes | + +`th-compat-0.1.4.patch` was miscounted so I left that out. +Simplified subsumption appears a lot but 3 are for Cabal, +so it's only 2 real occurrences. +We expect that to appear a lot however, +because it was one of *the* motivating changes for a [stability working group](https://blog.haskell.org/stability-working-group/). + +## Simplified subsumption +For the blissfully ignorant reader simplified subsumption causes you +to do this under certain existential conditions: +```haskell +--- a/Distribution/Simple/Utils.hs ++++ b/Distribution/Simple/Utils.hs +@@ -1338,7 +1338,7 @@ withTempFileEx opts tmpDir template action = + (\(name, handle) -> do hClose handle + unless (optKeepTempFiles opts) $ + handleDoesNotExist () . removeFile $ name) +- (withLexicalCallStack (uncurry action)) ++ (withLexicalCallStack (\x -> uncurry action x)) + +``` +You've to insert a lambda, which apparently signifies some performance impact. +This went wild with [Yesod stacks](https://www.yesodweb.com/book), +whose code generation helpfully created +the database alias in the template: +```haskell +type DB a = forall (m :: Type -> Type). + (MonadUnliftIO m) => ReaderT SqlBackend m a +``` + +So anything that now uses a query has to insert those lambdas, +as you can imagine this would be in quite a few places for non-trivial commercial code bases. +Which caused many issues for commercial users. +You can just delete those aliases to solve the problem. +Alternatively you can just enable the language extension: [DeepSubsumption](https://downloads.haskell.org/~ghc/9.12.2/docs/users_guide/exts/rank_polymorphism.html#extension-DeepSubsumption). +Which restores the original behavior. + +## Moving of instances due to Template Haskell +This change forces you to put the instances above the splice where +it's being used in the same module. +A dear colleague decided to generate instances in Template Haskell. +That was quite the puzzle! +I asked the GHC devs why they did this, +and it turns out this was a soundness issue in the typechecker. +Here, soundness means the type system can't be tricked into allowing invalid programs. +So the community is better off, despite this causing a fair bit of work. + +```haskell +--- a/src/Ipe/Content.hs ++++ b/src/Ipe/Content.hs +@@ -288,6 +288,14 @@ + ++instance Fractional r => IsTransformable (IpeObject r) where ++ transformBy t (IpeGroup i) = IpeGroup $ i&core %~ transformBy t ++ ... + makePrisms ''IpeObject + +@@ -303,14 +311,6 @@ + +-instance Fractional r => IsTransformable (IpeObject r) where +- transformBy t (IpeGroup i) = IpeGroup $ i&core %~ transformBy t +- ... +``` + +## (1) Parser change + +The parser is the component of the compiler that transforms text +into a memory structure the compiler can work with. +This structure is called an abstract syntax tree. + +```haskell +- Node4 {getMBB :: {-# UNPACK #-} ! MBB, getC1 :: ! (RTree a), getC2 :: ! (RTree a), getC3 :: ! (RTree a), getC4 :: ! (RTree a) } +- | Node3 {getMBB :: {-# UNPACK #-} ! MBB, getC1 :: ! (RTree a), getC2 :: ! (RTree a), getC3 :: ! (RTree a) } +- | Node2 {getMBB :: {-# UNPACK #-} ! MBB, getC1 :: ! (RTree a), getC2 :: ! (RTree a) } ++ Node4 {getMBB :: {-# UNPACK #-} !MBB, getC1 :: !(RTree a), getC2 :: !(RTree a), getC3 :: !(RTree a), getC4 :: !(RTree a) } ++ | Node3 {getMBB :: {-# UNPACK #-} !MBB, getC1 :: !(RTree a), getC2 :: !(RTree a), getC3 :: !(RTree a) } ++ | Node2 {getMBB :: {-# UNPACK #-} !MBB, getC1 :: !(RTree a), getC2 :: !(RTree a) } + | Node {getMBB :: MBB, getChildren' :: [RTree a] } +- | Leaf {getMBB :: {-# UNPACK #-} ! MBB, getElem :: a} ++ | Leaf {getMBB :: {-# UNPACK #-} !MBB, getElem :: a} + | Empty + +``` + +This is a change from all the way back in 2020, where the *core* language changed by +disallowing `!` before parens. +Here the bang `!` indicates strict fields. +Technically this doesn't fit into the category +because the core language isn't a language extension. +But semantics did change! +Actually I don't think we expected to find something like this at all. +I'm not sure how relevant this is to discuss further because it appears +quite rare for someone to do this. +You can enable [StrictData](https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/strict.html#extension-StrictData) +in your Cabal file and delete all those bangs! + +* (2) Rename forall identifiers +This changes the forall identifier into a keyword at term level. +It already was at the type level. +The issue is discussed [here](https://gitlab.haskell.org/ghc/ghc/-/issues/23719) + +```haskell + hintExplicitForall :: Located Token -> P () + hintExplicitForall tok = do +- forall <- getBit ExplicitForallBit ++ forAll <- getBit ExplicitForallBit + rulePrag <- getBit InRulePragBit +- unless (forall || rulePrag) $ addError $ mkPlainErrorMsgEnvelope (getLoc tok) $ ++ unless (forAll || rulePrag) $ addError $ mkPlainErrorMsgEnvelope (getLoc tok) $ + (PsErrExplicitForall (isUnicode tok)) +``` + +## TypeAbstractions + +From what I understand from the [manual](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_abstractions.html#type-abstractions) +Is that part of the syntax for type abstractions landed in GHC 9.2, +however 9.8 and onwards requires you to enable this language extension. +This appears to because certain new functionality was introduced behind an +old language extension flag, according to [this proposal](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0448-type-variable-scoping.rst#4type-arguments-in-constructor-patterns) + +This extension enables you to bind type variables in pattern matches. +I don't know why this happened like this, but it happened in 2023: + +```haskell ++-- Invisible type binders in type declarations, such as ++-- ++-- type family Sing @k ++-- ++-- require the TypeAbstractions extension. ++#if __GLASGOW_HASKELL__ >= 909 ++{-# LANGUAGE TypeAbstractions #-} ++#endif ++ +``` + +* Star is type +This change was announced via a warning. +It tells users to write `Type` instead of `*` for kinds representing types. +A kind is essentially the type of a type, +and as a concept is used for type-level programming type safety. + +```haskell +- type Basis v :: * ++ type Basis v :: Type +``` + + +* Conclusion + +Often we experience these breakages as annoying and frustrating. +However, if we look deeper, we find that each of them has +a little story +and good reasons for being introduced. +If you find this all as interesting as I do, +please consider joining some of the stability +working group meetings! + From 6ece3f22e74f83497caf6771ef372937b2f4959b Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Wed, 26 Nov 2025 17:50:33 +0100 Subject: [PATCH 02/21] add jappie --- content/investigating-lang-ext-semantics/index.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index c02f2d8..83200b6 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -1,3 +1,12 @@ ++++ +title = "Analyzing language extension semantics" +date = 2025-11-26 +[taxonomies] +authors = ["Jappie Klooster"] +categories = ["Haskell Foundation"] +tags = ["Community", "Stability"] ++++ + We analyzed the head.hackage patches to understand why code breaks on new GHC releases. Surprisingly, most breakage wasn’t caused by From b822d9595b544b583cd9c8ecadc1ce7f8603ee22 Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Wed, 26 Nov 2025 17:58:31 +0100 Subject: [PATCH 03/21] fix the headers --- content/investigating-lang-ext-semantics/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 83200b6..064aa28 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -148,7 +148,7 @@ quite rare for someone to do this. You can enable [StrictData](https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/strict.html#extension-StrictData) in your Cabal file and delete all those bangs! -* (2) Rename forall identifiers +## (2) Rename forall identifiers This changes the forall identifier into a keyword at term level. It already was at the type level. The issue is discussed [here](https://gitlab.haskell.org/ghc/ghc/-/issues/23719) @@ -187,7 +187,7 @@ I don't know why this happened like this, but it happened in 2023: + ``` -* Star is type +## Star is type This change was announced via a warning. It tells users to write `Type` instead of `*` for kinds representing types. A kind is essentially the type of a type, @@ -199,7 +199,7 @@ and as a concept is used for type-level programming type safety. ``` -* Conclusion +## Conclusion Often we experience these breakages as annoying and frustrating. However, if we look deeper, we find that each of them has From 46e879ae58be6917e27995b27d298d60b2dbcd0b Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Wed, 26 Nov 2025 18:11:55 +0100 Subject: [PATCH 04/21] Update content/investigating-lang-ext-semantics/index.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HĂ©cate --- content/investigating-lang-ext-semantics/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 064aa28..cbb0969 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -25,7 +25,7 @@ and learned surprisingly enough that the root cause of a lot of breakage isn't Template Haskell, but seems to be from language extension semantics[^meaning]. We're doing this investigation to better understand where the Haskell -Foundation stability working group should focus it's efforts. +Foundation stability working group should focus its efforts. About a year ago I started on analyzing the causes for all `head.hackage` patches, but I got bogged down by the sheer amount of work. From ddcff8dba5c8bb9092f9108b80e97e2a75acbdaa Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Wed, 26 Nov 2025 18:12:05 +0100 Subject: [PATCH 05/21] Update content/investigating-lang-ext-semantics/index.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HĂ©cate --- content/investigating-lang-ext-semantics/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index cbb0969..1506d86 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -73,7 +73,7 @@ to do this under certain existential conditions: + (withLexicalCallStack (\x -> uncurry action x)) ``` -You've to insert a lambda, which apparently signifies some performance impact. +You've to insert a lambda, which apparently has some performance impact. This went wild with [Yesod stacks](https://www.yesodweb.com/book), whose code generation helpfully created the database alias in the template: From aaaaf0b7a113b602842ae339f69599ac50f35a6b Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Wed, 26 Nov 2025 18:12:14 +0100 Subject: [PATCH 06/21] Update content/investigating-lang-ext-semantics/index.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HĂ©cate --- content/investigating-lang-ext-semantics/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 1506d86..f56f8d3 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -91,7 +91,7 @@ Which restores the original behavior. ## Moving of instances due to Template Haskell This change forces you to put the instances above the splice where -it's being used in the same module. +they are being used in the same module. A dear colleague decided to generate instances in Template Haskell. That was quite the puzzle! I asked the GHC devs why they did this, From 78a5627a3de22ff2eb6675e5ba5484643ede9fd2 Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Wed, 26 Nov 2025 18:12:35 +0100 Subject: [PATCH 07/21] Update content/investigating-lang-ext-semantics/index.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HĂ©cate --- content/investigating-lang-ext-semantics/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index f56f8d3..00b4e75 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -167,7 +167,7 @@ The issue is discussed [here](https://gitlab.haskell.org/ghc/ghc/-/issues/23719) ## TypeAbstractions From what I understand from the [manual](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_abstractions.html#type-abstractions) -Is that part of the syntax for type abstractions landed in GHC 9.2, +is that part of the syntax for type abstractions landed in GHC 9.2, however 9.8 and onwards requires you to enable this language extension. This appears to because certain new functionality was introduced behind an old language extension flag, according to [this proposal](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0448-type-variable-scoping.rst#4type-arguments-in-constructor-patterns) From 0c9506570f47364533bc374166eb6165c633a023 Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Wed, 26 Nov 2025 18:36:52 +0100 Subject: [PATCH 08/21] Update content/investigating-lang-ext-semantics/index.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HĂ©cate --- content/investigating-lang-ext-semantics/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 00b4e75..9fa80a2 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -169,7 +169,7 @@ The issue is discussed [here](https://gitlab.haskell.org/ghc/ghc/-/issues/23719) From what I understand from the [manual](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_abstractions.html#type-abstractions) is that part of the syntax for type abstractions landed in GHC 9.2, however 9.8 and onwards requires you to enable this language extension. -This appears to because certain new functionality was introduced behind an +This appeared because certain new functionality was introduced behind an old language extension flag, according to [this proposal](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0448-type-variable-scoping.rst#4type-arguments-in-constructor-patterns) This extension enables you to bind type variables in pattern matches. From 4649f0fc7b9220164193321acd8d2af8d95317f2 Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Wed, 26 Nov 2025 18:37:10 +0100 Subject: [PATCH 09/21] Update content/investigating-lang-ext-semantics/index.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HĂ©cate --- content/investigating-lang-ext-semantics/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 9fa80a2..8522bd1 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -170,7 +170,7 @@ From what I understand from the [manual](https://ghc.gitlab.haskell.org/ghc/doc/ is that part of the syntax for type abstractions landed in GHC 9.2, however 9.8 and onwards requires you to enable this language extension. This appeared because certain new functionality was introduced behind an -old language extension flag, according to [this proposal](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0448-type-variable-scoping.rst#4type-arguments-in-constructor-patterns) +old language extension flag, according to [this proposal](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0448-type-variable-scoping.rst#4type-arguments-in-constructor-patterns). This extension enables you to bind type variables in pattern matches. I don't know why this happened like this, but it happened in 2023: From 6e4c9e1e734845dab798adbb389f37df8695ab19 Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sat, 29 Nov 2025 22:32:23 +0100 Subject: [PATCH 10/21] fix diff syntax --- content/investigating-lang-ext-semantics/index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 8522bd1..48448fa 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -62,7 +62,7 @@ because it was one of *the* motivating changes for a [stability working group](h ## Simplified subsumption For the blissfully ignorant reader simplified subsumption causes you to do this under certain existential conditions: -```haskell +```diff --- a/Distribution/Simple/Utils.hs +++ b/Distribution/Simple/Utils.hs @@ -1338,7 +1338,7 @@ withTempFileEx opts tmpDir template action = @@ -77,7 +77,7 @@ You've to insert a lambda, which apparently has some performance impact. This went wild with [Yesod stacks](https://www.yesodweb.com/book), whose code generation helpfully created the database alias in the template: -```haskell +```diff type DB a = forall (m :: Type -> Type). (MonadUnliftIO m) => ReaderT SqlBackend m a ``` @@ -99,7 +99,7 @@ and it turns out this was a soundness issue in the typechecker. Here, soundness means the type system can't be tricked into allowing invalid programs. So the community is better off, despite this causing a fair bit of work. -```haskell +```diff --- a/src/Ipe/Content.hs +++ b/src/Ipe/Content.hs @@ -288,6 +288,14 @@ @@ -122,7 +122,7 @@ The parser is the component of the compiler that transforms text into a memory structure the compiler can work with. This structure is called an abstract syntax tree. -```haskell +```diff - Node4 {getMBB :: {-# UNPACK #-} ! MBB, getC1 :: ! (RTree a), getC2 :: ! (RTree a), getC3 :: ! (RTree a), getC4 :: ! (RTree a) } - | Node3 {getMBB :: {-# UNPACK #-} ! MBB, getC1 :: ! (RTree a), getC2 :: ! (RTree a), getC3 :: ! (RTree a) } - | Node2 {getMBB :: {-# UNPACK #-} ! MBB, getC1 :: ! (RTree a), getC2 :: ! (RTree a) } @@ -153,7 +153,7 @@ This changes the forall identifier into a keyword at term level. It already was at the type level. The issue is discussed [here](https://gitlab.haskell.org/ghc/ghc/-/issues/23719) -```haskell +```diff hintExplicitForall :: Located Token -> P () hintExplicitForall tok = do - forall <- getBit ExplicitForallBit @@ -175,7 +175,7 @@ old language extension flag, according to [this proposal](https://github.com/ghc This extension enables you to bind type variables in pattern matches. I don't know why this happened like this, but it happened in 2023: -```haskell +```diff +-- Invisible type binders in type declarations, such as +-- +-- type family Sing @k @@ -193,7 +193,7 @@ It tells users to write `Type` instead of `*` for kinds representing types. A kind is essentially the type of a type, and as a concept is used for type-level programming type safety. -```haskell +```diff - type Basis v :: * + type Basis v :: Type ``` From 2b44c6e30e2deb8d961c7d1d17109b5236bbc700 Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sat, 29 Nov 2025 22:38:20 +0100 Subject: [PATCH 11/21] add notes on head.hackage --- content/investigating-lang-ext-semantics/index.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 48448fa..eaf81e2 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -7,8 +7,13 @@ categories = ["Haskell Foundation"] tags = ["Community", "Stability"] +++ -We analyzed the head.hackage patches to understand +We analyzed the [head.hackage](https://gitlab.haskell.org/ghc/head.hackage)[^head.hackage] patches to understand why code breaks on new GHC releases. +"head.hackage" is a repository of patches for Hackage. +GHC engineers use these to test out new GHC builds on a wide range of +Hackage packages without having to upstream[^upstream] a patch, which can take time. +Instead, they can put the patch in "head.hackage" +and immediately test it on a wide range of packages. Surprisingly, most breakage wasn’t caused by Template Haskell—it came from deeper semantic changes in language extensions. This post walks through the main categories of breakage, @@ -16,6 +21,8 @@ why they happened, and what they tell us about long-term stability. If you care about a smoother upgrade path for Haskell users, we invite you to participate in the Stability Working Group. +[^upstream]: Upstreaming is the process of sending a patch to the “maintainers” of an open-source project. The maintainers will then make the patch ‘official’ by merging it. In principle, the process is simple, but in practice, the burden of proof (especially for larger projects) is on the person who submitted the patch. They have to convince the maintainers that the patch is useful, which takes time in the form of communication + The [Haskell Foundation Stability Working Group](https://blog.haskell.org/stability-working-group/) is interested in understanding why breakage occurs. From 1a5ba6de3805f02548c8ad016fbcdeb6cdc7f191 Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sat, 29 Nov 2025 22:38:57 +0100 Subject: [PATCH 12/21] Fix markdown rendere --- content/investigating-lang-ext-semantics/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index eaf81e2..817f713 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -58,7 +58,7 @@ This gave us the following table: | hgeometry-ipe-0.13.patch | Instances moved due to splice enforcement | no | | singletons-3.0.2.patch | add TypeAbstractions as a language extension | yes | | singletons-base-3.1.1.patch | add TypeAbstractions as a language extension | yes | -| vector-space-0.16.patch | * is type (4) | yes | +| vector-space-0.16.patch | Star is type (4) | yes | `th-compat-0.1.4.patch` was miscounted so I left that out. Simplified subsumption appears a lot but 3 are for Cabal, From 47fbea7bb8ae6600dbd7f70b457b02272a48e5ad Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sat, 29 Nov 2025 22:39:17 +0100 Subject: [PATCH 13/21] spacing --- content/investigating-lang-ext-semantics/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 817f713..30e14cb 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -58,7 +58,7 @@ This gave us the following table: | hgeometry-ipe-0.13.patch | Instances moved due to splice enforcement | no | | singletons-3.0.2.patch | add TypeAbstractions as a language extension | yes | | singletons-base-3.1.1.patch | add TypeAbstractions as a language extension | yes | -| vector-space-0.16.patch | Star is type (4) | yes | +| vector-space-0.16.patch | Star is type (4) | yes | `th-compat-0.1.4.patch` was miscounted so I left that out. Simplified subsumption appears a lot but 3 are for Cabal, From 3fac6abb6e9493ea2331cf675ac94ae3dd3c2f91 Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sat, 29 Nov 2025 23:10:00 +0100 Subject: [PATCH 14/21] Go over again --- .../investigating-lang-ext-semantics/index.md | 50 ++++++++----------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 30e14cb..972fc0c 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -7,7 +7,8 @@ categories = ["Haskell Foundation"] tags = ["Community", "Stability"] +++ -We analyzed the [head.hackage](https://gitlab.haskell.org/ghc/head.hackage)[^head.hackage] patches to understand +Hi I'm [Jappie](https://jappie.me) and I volunteer for the [Haskell Foundation Stability Working Group](https://blog.haskell.org/stability-working-group/). +Recently we analyzed the [head.hackage](https://gitlab.haskell.org/ghc/head.hackage)[^head.hackage] patches to understand why code breaks on new GHC releases. "head.hackage" is a repository of patches for Hackage. GHC engineers use these to test out new GHC builds on a wide range of @@ -15,7 +16,9 @@ Hackage packages without having to upstream[^upstream] a patch, which can take t Instead, they can put the patch in "head.hackage" and immediately test it on a wide range of packages. Surprisingly, most breakage wasn’t caused by -Template Haskell—it came from deeper semantic changes in language extensions. +[Template Haskell](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/template_haskell.html), +it came from deeper semantic changes in language extensions. +The meaning of (some) language extensions changed between GHC releases. This post walks through the main categories of breakage, why they happened, and what they tell us about long-term stability. If you care about a smoother upgrade path for Haskell users, @@ -23,25 +26,14 @@ we invite you to participate in the Stability Working Group. [^upstream]: Upstreaming is the process of sending a patch to the “maintainers” of an open-source project. The maintainers will then make the patch ‘official’ by merging it. In principle, the process is simple, but in practice, the burden of proof (especially for larger projects) is on the person who submitted the patch. They have to convince the maintainers that the patch is useful, which takes time in the form of communication -The [Haskell Foundation Stability Working Group](https://blog.haskell.org/stability-working-group/) -is interested in understanding -why breakage occurs. -This is an extension of our initial [investigation](https://jappie.me/analyzing-haskell-stability.html). -We've recently done further analysis on [head.hackage](https://ghc.gitlab.haskell.org/head.hackage/), +Extending our initial [investigation](https://jappie.me/analyzing-haskell-stability.html), +We're also interested in understanding *why* breakage occurs. +So we've recently done further analysis on [head.hackage](https://ghc.gitlab.haskell.org/head.hackage/), and learned surprisingly enough that the root cause of a lot of breakage isn't Template Haskell, but seems to be from language extension semantics[^meaning]. -We're doing this investigation to better understand where the Haskell -Foundation stability working group should focus its efforts. -About a year ago I started on analyzing the causes -for all `head.hackage` patches, but I got bogged down -by the sheer amount of work. -Trevis suggested focusing on the -most important question, -why do language extension semantics cause so much breakage? -So instead of being bogged down by analyzing all -the patches, I just looked at the 12 patches from language extension -semantics. +We're doing this investigation to understand better where efforts +should be focused in improving stability. [^meaning]: The precise meaning of features enabled by language extensions. I guess parser changes also count. @@ -63,7 +55,7 @@ This gave us the following table: `th-compat-0.1.4.patch` was miscounted so I left that out. Simplified subsumption appears a lot but 3 are for Cabal, so it's only 2 real occurrences. -We expect that to appear a lot however, +I'd expect that to appear a lot however, because it was one of *the* motivating changes for a [stability working group](https://blog.haskell.org/stability-working-group/). ## Simplified subsumption @@ -80,8 +72,8 @@ to do this under certain existential conditions: + (withLexicalCallStack (\x -> uncurry action x)) ``` -You've to insert a lambda, which apparently has some performance impact. -This went wild with [Yesod stacks](https://www.yesodweb.com/book), +You have to insert a lambda, which apparently has some performance impact. +This had a big impact on [Yesod stacks](https://www.yesodweb.com/book), whose code generation helpfully created the database alias in the template: ```diff @@ -89,11 +81,13 @@ type DB a = forall (m :: Type -> Type). (MonadUnliftIO m) => ReaderT SqlBackend m a ``` -So anything that now uses a query has to insert those lambdas, -as you can imagine this would be in quite a few places for non-trivial commercial code bases. -Which caused many issues for commercial users. -You can just delete those aliases to solve the problem. -Alternatively you can just enable the language extension: [DeepSubsumption](https://downloads.haskell.org/~ghc/9.12.2/docs/users_guide/exts/rank_polymorphism.html#extension-DeepSubsumption). +Normally this is quite convenient, +however with the simplified subsumption change, +any code that interacts with the database now has to insert those lambdas. +As you can imagine this would in many places for a commercial code base. +Causing a lot of compile errors for industrial users. +Instead of inserting lambdas, you can also delete those existential aliases to solve the problem. +Or you can enable the language extension: [DeepSubsumption](https://downloads.haskell.org/~ghc/9.12.2/docs/users_guide/exts/rank_polymorphism.html#extension-DeepSubsumption). Which restores the original behavior. ## Moving of instances due to Template Haskell @@ -208,8 +202,8 @@ and as a concept is used for type-level programming type safety. ## Conclusion -Often we experience these breakages as annoying and frustrating. -However, if we look deeper, we find that each of them has +Often these breakages are annoying and frustrating. +But if we look deeper, we find that each of them has a little story and good reasons for being introduced. If you find this all as interesting as I do, From 1a90f32e7e33d370d4a4d0b2f5dd49db0f861b4d Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sat, 29 Nov 2025 23:18:14 +0100 Subject: [PATCH 15/21] Fixup some minor errors. nice and thight --- content/investigating-lang-ext-semantics/index.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 972fc0c..ae43828 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -8,7 +8,7 @@ tags = ["Community", "Stability"] +++ Hi I'm [Jappie](https://jappie.me) and I volunteer for the [Haskell Foundation Stability Working Group](https://blog.haskell.org/stability-working-group/). -Recently we analyzed the [head.hackage](https://gitlab.haskell.org/ghc/head.hackage)[^head.hackage] patches to understand +Recently we analyzed the [head.hackage](https://gitlab.haskell.org/ghc/head.hackage) patches to understand why code breaks on new GHC releases. "head.hackage" is a repository of patches for Hackage. GHC engineers use these to test out new GHC builds on a wide range of @@ -22,7 +22,7 @@ The meaning of (some) language extensions changed between GHC releases. This post walks through the main categories of breakage, why they happened, and what they tell us about long-term stability. If you care about a smoother upgrade path for Haskell users, -we invite you to participate in the Stability Working Group. +we invite you to participate in the [Haskell Foundation Stability Working Group](https://blog.haskell.org/stability-working-group/). [^upstream]: Upstreaming is the process of sending a patch to the “maintainers” of an open-source project. The maintainers will then make the patch ‘official’ by merging it. In principle, the process is simple, but in practice, the burden of proof (especially for larger projects) is on the person who submitted the patch. They have to convince the maintainers that the patch is useful, which takes time in the form of communication @@ -76,7 +76,7 @@ You have to insert a lambda, which apparently has some performance impact. This had a big impact on [Yesod stacks](https://www.yesodweb.com/book), whose code generation helpfully created the database alias in the template: -```diff +```haskell type DB a = forall (m :: Type -> Type). (MonadUnliftIO m) => ReaderT SqlBackend m a ``` @@ -171,7 +171,9 @@ From what I understand from the [manual](https://ghc.gitlab.haskell.org/ghc/doc/ is that part of the syntax for type abstractions landed in GHC 9.2, however 9.8 and onwards requires you to enable this language extension. This appeared because certain new functionality was introduced behind an -old language extension flag, according to [this proposal](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0448-type-variable-scoping.rst#4type-arguments-in-constructor-patterns). +old language extension flag, according to [this proposal](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0448-type-variable-scoping.rst#4type-arguments-in-constructor-patterns). It says we don't want to introduce new functionality behind established extensions, +so that's why we require TypeAbstractions now, +where previously ScopedTypeVariables and TypeApplications were enough. This extension enables you to bind type variables in pattern matches. I don't know why this happened like this, but it happened in 2023: From e16b382c7174502ed0c6f13517522d617bd4d35b Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sat, 29 Nov 2025 23:19:24 +0100 Subject: [PATCH 16/21] fix table 2 --- content/investigating-lang-ext-semantics/index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index ae43828..758e618 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -40,7 +40,7 @@ should be focused in improving stability. This gave us the following table: | Name | Cause | Had warnings? | -|-------------------------------------+----------------------------------------------+---------------| +|-------------------------------------|----------------------------------------------|---------------| | Cabal-2.4.1.0.patch | simplified subsumption | no | | Cabal-3.0.2.0.patch | simplified subsumption | no | | Cabal-3.2.1.0.patch | simplified subsumption | no | @@ -52,6 +52,8 @@ This gave us the following table: | singletons-base-3.1.1.patch | add TypeAbstractions as a language extension | yes | | vector-space-0.16.patch | Star is type (4) | yes | +|-- + `th-compat-0.1.4.patch` was miscounted so I left that out. Simplified subsumption appears a lot but 3 are for Cabal, so it's only 2 real occurrences. From e8b4dff41ac22ce24c8e07ae645b8b27dac1436e Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sun, 30 Nov 2025 16:51:30 +0100 Subject: [PATCH 17/21] Update content/investigating-lang-ext-semantics/index.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HĂ©cate --- content/investigating-lang-ext-semantics/index.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 758e618..e1b2956 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -52,8 +52,6 @@ This gave us the following table: | singletons-base-3.1.1.patch | add TypeAbstractions as a language extension | yes | | vector-space-0.16.patch | Star is type (4) | yes | -|-- - `th-compat-0.1.4.patch` was miscounted so I left that out. Simplified subsumption appears a lot but 3 are for Cabal, so it's only 2 real occurrences. From b93f54917482e8c9c5d1c514f1551aa2f3a1568d Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sun, 30 Nov 2025 16:51:37 +0100 Subject: [PATCH 18/21] Update content/investigating-lang-ext-semantics/index.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HĂ©cate --- content/investigating-lang-ext-semantics/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index e1b2956..e1eb979 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -165,7 +165,7 @@ The issue is discussed [here](https://gitlab.haskell.org/ghc/ghc/-/issues/23719) (PsErrExplicitForall (isUnicode tok)) ``` -## TypeAbstractions +## (3) TypeAbstractions From what I understand from the [manual](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_abstractions.html#type-abstractions) is that part of the syntax for type abstractions landed in GHC 9.2, From b524d8c9884f0ead85173fea3ce3e6e08a63176f Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sun, 30 Nov 2025 16:51:45 +0100 Subject: [PATCH 19/21] Update content/investigating-lang-ext-semantics/index.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HĂ©cate --- content/investigating-lang-ext-semantics/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index e1eb979..c81bd2b 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -190,7 +190,7 @@ I don't know why this happened like this, but it happened in 2023: + ``` -## Star is type +## (4) Star is type This change was announced via a warning. It tells users to write `Type` instead of `*` for kinds representing types. A kind is essentially the type of a type, From 0064ce195fd0274e8873723fb7d3181126276fbb Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sun, 30 Nov 2025 16:51:55 +0100 Subject: [PATCH 20/21] Update content/investigating-lang-ext-semantics/index.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HĂ©cate --- content/investigating-lang-ext-semantics/index.md | 1 - 1 file changed, 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index c81bd2b..8fa4052 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -201,7 +201,6 @@ and as a concept is used for type-level programming type safety. + type Basis v :: Type ``` - ## Conclusion Often these breakages are annoying and frustrating. From ef452525cb2319d28448fa5143eb2a5e1d14eb4d Mon Sep 17 00:00:00 2001 From: Jappie Klooster Date: Sun, 30 Nov 2025 16:52:04 +0100 Subject: [PATCH 21/21] Update content/investigating-lang-ext-semantics/index.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HĂ©cate --- content/investigating-lang-ext-semantics/index.md | 1 - 1 file changed, 1 deletion(-) diff --git a/content/investigating-lang-ext-semantics/index.md b/content/investigating-lang-ext-semantics/index.md index 8fa4052..e6deb7d 100644 --- a/content/investigating-lang-ext-semantics/index.md +++ b/content/investigating-lang-ext-semantics/index.md @@ -210,4 +210,3 @@ and good reasons for being introduced. If you find this all as interesting as I do, please consider joining some of the stability working group meetings! -