Re: Let's fix static pointers (#26556)
Anyway, I'll not push my pet peeve further :) (Though I do think it's an important peeve :).
Why not? If you have a good idea, push it.
Hmm. So your proposal is:
- When declaring a type T, specify it as a "non-updatable type"
- A thunk of type T is call-by-name
So in
let foo = f 200 in g foo foo
We'd expect (f 200) to be evaluated as many times as `g` evaluates either
argument. Right?
What about
let bar:: Maybe T = Just (f 200) ] in ...
Is bar call-by-name? Somehow wrapping in Just shouldn't change too much?
Simon
On Tue, 3 Feb 2026 at 08:25, Edsko de Vries
I think there *is* a better answer: I think (but this will require careful consideration) that the way is is to be able to mark certain types as "never update thunks of this type".
Anyway, I'll not push my pet peeve further :) (Though I do think it's an important peeve :).
-E On 2/2/26 11:06, Simon Peyton Jones wrote:
PS. If you'll allow me to be cheeky for a moment: if you did want a much
more fundamental problem to sink your teeth into, may I interest you in the need for call-by-name in Haskell (as opposed to call-by-need)? I've been writing and talking about this for almost 10 years now ( https://well-typed.com/blog/2016/09/sharing-conduit/, HIW 2023, in Seattle, IFL 2024, ..;
The question of when to share and when to recompute is indeed a hard one. I wrote about it in my 1987 book https://simon.peytonjones.org/slpj-book-1987/, Chapter 23. I know of no automated solution. Lacking that we need
- Tools for identifying the problem - Ways of saying what we want in those cases
The `oneShot` primitive allows you to say "use call by name for this lambda"; we use that in GHC. You didn't mention it in your post.
I wish I had a better answer!
Simon
On Sat, 31 Jan 2026 at 06:15, Edsko de Vries
wrote: Hi Simon,
The main pain point you identify is the "parallel set of instances". I wonder if there a design that could automate that? I think we discussed something along those lines years ago but I have fully paged it out.
Also I wonder if there are enough compelling applications to justify it. Or if a good solution would unlock new applications.
I'm pretty sure that that is a solvable problem, but honestly I don't think it's currently worth spending a lot of effort on it, given the unfortunately low adaption of static pointers. If you did want to improve the situation with static pointers, I think a more low hanging fruit would be to make StaticKeys more stable: across recompilation (with source code changes), across platforms, etc. I think that would make static pointers a bit more useful when deployed in a Cloud Haskell-like scenario, and perhaps spark some more interest.
PS. If you'll allow me to be cheeky for a moment: if you did want a much more fundamental problem to sink your teeth into, may I interest you in the need for call-by-name in Haskell (as opposed to call-by-need)? I've been writing and talking about this for almost 10 years now ( https://well-typed.com/blog/2016/09/sharing-conduit/, HIW 2023, in Seattle, IFL 2024, ..; I also semi co-supervised a masters student on this topic), but I think nothing much has really changed yet. I think is an important problem (it affects real code that people write today) and very foundational (I don't know of any solutions to it, other than hacks like https://hackage.haskell.org/package/dupIO, our revival of Joachim's old package). I was going to send you my slides for my IFL talk but actually I think they don't make much sense without the accompanying presentation. If this at all sounds of interest, let me know and I can try to write down a summary of the problem (also happy to have a call if that's preferable).
Have a good weekend :)
Edsko
warm good wishes
Simon
On Wed, 21 Jan 2026 at 17:43, Edsko de Vries
wrote: Hi Simon,
I'll look out for the Unfoldr episode -- if you remember do send me a link
The link is https://www.youtube.com/watch?v=Mc3liw0EoIY&list=PLD8gywOEY4HaG5VSrKVnHxCptlJv2GAn7&index=54 ; it will air live tonight at 7:30pm GMT, 8:30pm CET, and will be available at the same link thereafter.
-Edsko
On 1/9/26 16:21, Simon Peyton Jones wrote:
Great thanks Edsko -- v helpful.
By the way, I am not entirely sure why the proposal mentions the
IsStatic class explicitly, but I just wanted to mention that it is really quite useful; I declare two instances in my Unfolder episode :)
The proposal mentions IsStatic only because I wanted to give the expansion that GHC performs. I agree that it's useful, but it's really orthogonal to the proposal.
I'll look out for the Unfoldr episode -- if you remember do send me a link
Thanks again
Simon
On Fri, 9 Jan 2026 at 12:25, Edsko de Vries
wrote: Hi Simon,
Apologies for my slow reply. It's been nearly 15 years (!) since I wrote distributed-process and distributed-static (fun fact: it was my first task when I joined Well-Typed) and I felt I needed to set aside some time to properly page everything back in, as I haven't really used static pointers ever since (somewhat sadly..). I realize that the proposal how now been written and accepted, so this email comes too late in a way, but I finally had some time now to look at this properly. In hindsight, the proposal is simple enough, but I didn't realize that at the time.
Anyway. The proposal is fine with me :) I seems to me that it's no more than a minor inconvenience to the programmer. I have now prepared a Haskell Unfolder episode (which will likely be the next episode we air, episode #53) on static pointers, where I discuss the primitive building blocks provided by ghc, and show how we can implement some compositional infrastructure around that (including some thoughts on type classes, which I believe you and I discussed a very long time ago). I'll also mention the proposal and the upcoming changes in 9.14.2 (warning) and 9.16 (change implemented). I tried building it with your branch (wip/T26718) and that all worked fine. I also added one module in which your branch issues the warning expect, and that seemed good also.
By the way, I am not entirely sure why the proposal mentions the IsStatic class explicitly, but I just wanted to mention that it is really quite useful; I declare two instances in my Unfolder episode :)
-Edsko On 11/19/25 13:02, Simon Peyton Jones wrote:
Thanks Laurent and Mathieu
*Edsko, Duncan, Facundo*: do you have any views?
I have now written a GHC proposal https://github.com/ghc-proposals/ghc-proposals/blob/wip/spj-static/proposals.... Could you add your thoughts to it? I think it's a no-brainer myself, but we should go through the proper process.
Do any of you know people I should consult? Eg authors of libraries that use StaticPtrs?
Thanks
Simon
On Fri, 7 Nov 2025 at 21:18, Simon Peyton Jones < simon.peytonjones@gmail.com> wrote:
Dear Laurent, Duncan, Mathieu, Facundo, Edsko
I have spent a little while digging into *static pointers* recently. See my post below. I wonder if you have any comments on my proposal?
Do you know anyone else I should consult?
Thanks!
Simon
On Fri, 7 Nov 2025 at 18:13, Simon Peyton Jones (@simonpj) < gitlab@gitlab.haskell.org> wrote:
Simon Peyton Jones https://gitlab.haskell.org/simonpj created an issue: #26556 https://gitlab.haskell.org/ghc/ghc/-/issues/26556
Static pointers are not properly implemented. For example:
- #26545 https://gitlab.haskell.org/ghc/ghc/-/issues/26545 - #24464 https://gitlab.haskell.org/ghc/ghc/-/issues/24464 - #24773 https://gitlab.haskell.org/ghc/ghc/-/issues/24773
among others. Moreover, the implementation is very messy, scattered about in FloatOut and elsewhere.
Let's fix it. Discussion
I embarked on what I thought would be a simple refactor to
- Identify static bindings in the type checker - Promote them to top level desugarer
thereby avoiding all the terribly painful static-form-floating stuff that been an ongoing source of breakage and irritation.
Sadly it was not as simple as I had thought. Merge request !14994 https://gitlab.haskell.org/ghc/ghc/-/merge_requests/14994 is my work in progress
-
At first it seems simple: given static e - When typechecking e ensure that all its free variables are top-level defined - When desugaring, move e to top level
Apparently simple! -
*Complication 1*. e might generate constraints. We don't want to solve those from locally-bound Givens, because they'll be out of scope when we promote to top level.
Solution: wrap the constraints in an implication with SkolInfo of StaticFormSkol; and in the constraint solver zap all Givens when walking inside such an implication. That was done in
commit 39d4a24beaa7874a69ffdc1528ca160818829169Author: Simon Peyton Jones
Date: Tue Sep 30 23:11:19 2025 +0100 Build implication for constraints from (static e) This commit addresses #26466, by buiding an implication for the constraints arising from a (static e) form. The implication has a special ic_info field of StaticFormSkol, which tells the constraint solver to use an empty set of Givens. So that complication wasn't at all bad. -
*Complication 2*. What if we have
f x = let y = reverse "hello" in ...(static (y++y))...
The free vars of the static are just {y}, and y is morally-top-level. It in turn has no free variables.
Sadly (as it turns out) GHC tries to accept this case. When looking at the defn of y (with no static in sight yet) the typechecker marks it at a "static binding", meaning that it too can (and indeed must) be floated to top level.
So if the desugarer moves the static to the top level, it must move y too. And that means it must mark the typechecked binding in some way, so the desugarer can identify it. Not so hard, but there is quite a bit of new plumbing. -
*Complication 3*. But what if y's RHS generates constraints, which use Givens (or solved dictionaries, which are very similar) from its context. E.g.
f x = let p = x+1::Int; y = 2+3::Int in ...
Now there may be a d :: Num Int lying around from dealing with p, and y may use it. Oh no! Now that'll be out of scope if we move y to top level.
Plausible solution: use them same mechanism for static bindings as we did for static e expressions. That is, build an implication constraint whose SkolInfo says "zap Givens". This turned out to be considerably harder to implement than it was for Complication 1. -
*Complication 4*. What if y is not generalised, perhaps because of the Monomorphism Restriction? e.g.
f :: Num a => a -> blahf x = let y = 3+3 in (x+y, static( ..y.. ))
Now y is monomorphic and really does use the dictionary passed to f. So it really cannot appear in the static. Somehow y really isn't static after all. We must reject this program. Not only is it an implementation mess (Complications 1,2,3 are already imposing quite a signficant implemenation burden) but it becomes pretty hard to explain to the programmer just which uses of static are OK and which are not.
What a swamp. At this point I threw up my hands and wrote this summary
Proposal
To me the solution is clear: the rule should be
- *in static e, all the free vars of e should be bound at top level*
That is a nice simple rule; it is easy to explain and easy to implement. It is also what the user manual says!
In retrospect, by addressing Complication 2 I was trying too hard! (And this extra feature is entirely undocumented.) I thought that I could deal with Complication 2 using the same mechanism as the one that deals with MonoLocalBinds. But I was wrong.
Making this change could perhaps break some programs. They would all be easy to fix, by moving bindings for any free variables to top level. But note that the status quo is not stable: it has bugs e.g #24464 https://gitlab.haskell.org/ghc/ghc/-/issues/24464, #26545 https://gitlab.haskell.org/ghc/ghc/-/issues/26545. What we have is at attempt to be clever that is simply wrong.
— View it on GitLab https://gitlab.haskell.org/ghc/ghc/-/issues/26556.
You're receiving this email because of your activity on gitlab.haskell.org. Unsubscribe https://gitlab.haskell.org/-/sent_notifications/4b29fc65ccdc21e95267b66fdfb6... from this thread · Manage all notifications https://gitlab.haskell.org/-/profile/notifications · Help https://gitlab.haskell.org/help
Isn't a function of type `(# #) -> T` already (isomorphic to) a "non-updatable thunk" of type T? Tom On Tue, Feb 03, 2026 at 11:43:48AM +0000, Simon Peyton Jones via ghc-devs wrote:
Hmm. So your proposal is:
- When declaring a type T, specify it as a "non-updatable type" - A thunk of type T is call-by-name
On Tue, 3 Feb 2026 at 08:25, Edsko de Vries
wrote: I think there *is* a better answer: I think (but this will require careful consideration) that the way is is to be able to mark certain types as "never update thunks of this type".
It is. However that doesn't give users the ability to solve this since -ffull-lazines allows GHC to potentially turn such a function into a thunk. One can disable full-laziness to avoid this, but you might like some things to be floated/shared and others to be constrained to a certain context rather than retaining them. Even if GHC would leave the function itself one might end up with code like: foo::Void#->T foo =... bar x y z = letr =foo void# in(f r,f r) Now foo itself is a "non-updatable thunk". But we capture it's result in an updatable thunk. Hence the need for the non-updatable attribute to be transitive in some fashion. Associating it with the return type might be one solution to address this. On 03/02/2026 13:34, Tom Ellis via ghc-devs wrote:
Isn't a function of type `(# #) -> T` already (isomorphic to) a "non-updatable thunk" of type T?
Tom
On Tue, Feb 03, 2026 at 11:43:48AM +0000, Simon Peyton Jones via ghc-devs wrote:
Hmm. So your proposal is:
- When declaring a type T, specify it as a "non-updatable type" - A thunk of type T is call-by-name
On Tue, 3 Feb 2026 at 08:25, Edsko de Vries
wrote: I think there *is* a better answer: I think (but this will require careful consideration) that the way is is to be able to mark certain types as "never update thunks of this type".
ghc-devs mailing list --ghc-devs@haskell.org To unsubscribe send an email toghc-devs-leave@haskell.org
participants (3)
-
Andreas Klebinger -
Simon Peyton Jones -
Tom Ellis