Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to GHC 9.2.1 #1342

Closed
wants to merge 4 commits into from
Closed

Upgrade to GHC 9.2.1 #1342

wants to merge 4 commits into from

Conversation

zacwood9
Copy link
Collaborator

@zacwood9 zacwood9 commented Feb 2, 2022

Some initial notes:

Html type

In previous versions Html was defined as

Html ~ HtmlWithContext ControllerContext ~ ((?context :: ControllerContext) => Blaze.Html)

In GHC 9+ this causes a lot of issues with the type checker due to "simplified subsupmption".

This PR changes the Html type to remove the added context.

old: func :: Html
new: func :: (?context :: ControllerContext) => Html

Maybe there's a way around this? One idea could be a type family

type family InContext a where
   InContext a = (?context :: ControllerContext) => a

that could be used to reduce some verbosity

layout :: InContext (Html -> Html)

If there's no way around this due to the new type checker, I plan to work on an automated tool for this upgrade since it's pretty tedious adding the constraints everywhere.

HLS Support

HLS support isn't working right now. The version with GHC 9.2.1 support (1.6.1.0) isn't in nixpkgs yet so I added a section to build it manually: https://github.com/digitallyinduced/ihp/pull/1342/files#diff-a4db1b622d10fd4ea165375e6f9ca4b289d8eae007d8bf58c6c41498bed281bcR64

Notice this doesn't include the --enable-dynamic-executable flag which speeds things up on Linux. With that flag this build seems to break. Unclear why. Need to research more here or hopefully wait for 1.6.1.0 to land in Nixpkgs and hope that works?

@mpscholten
Copy link
Member

Just did a first look through the changes. Here's a few thoughts:

  • The diff is very large right now and contains many changes that are not strictly related to GHC9.2.1 (e.g. a lot of whitespace changes / reformatting)
  • On the Html problem: We should report this issue upstream to the GHC GitLab. Maybe it could be fixed by adding a special case for implicit parameters to the constraint? I'd try to avoid making all view type sigs more verbose.

@@ -15,7 +19,7 @@ data DataSyncMessage
| CreateRecordsMessage { table :: !Text, records :: ![HashMap Text Value], requestId :: !Int }
| UpdateRecordMessage { table :: !Text, id :: !UUID, patch :: !(HashMap Text Value), requestId :: !Int }
| DeleteRecordMessage { table :: !Text, id :: !UUID, requestId :: !Int }
deriving (Eq, Show)
deriving (Eq, Show, Generic, FromJSON)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is Generics needed here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were quite a few errors in the DataSync files about "no FromJSON" implementation - it appears something changed with ordering of TH splices now mattering in terms of if an implementation could be found.

So this was a quick fix to get a FromJSON instance that worked -- definitely not needed. Meant to remove this before pushing!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that makes sense 👍 I try to avoid Generics wherever possible because of the compile time slowdown, therefore this directly caught my eyes :)

@zacwood9
Copy link
Collaborator Author

zacwood9 commented Mar 4, 2022

Sorry for the silence here - I'm done moving across the country so I am looking at this again :)

Minimal reproduction of the behavior

I was able to come up with a minimal reproduction for the behavior of the new type checker in GHC 9.2:

type T = (?a :: Int) => IO ()

f :: [String] -> T
f xs = mapM_ g xs

g :: String -> T
g _ = pure ()

This fails:

• Couldn't match type: (?a::Int) => IO ()
                     with: IO b0
      Expected: String -> IO b0
        Actual: String -> T

but, the GHC devs actually knew this program would break. It turns out the transformations going on under the hood to get this to type check before were actually unsound and could change the semantics of the program.

Recommended fix by GHC devs

The recommended fix is to "just" manually eta-expand:

f :: [String] -> T
f xs = mapM_ (\x -> g x) xs

which for reasons beyond me, but also known by the GHC devs, type checks safely and correctly.

I don't think this is a "bug" that should be reported to GHC, since the behavior is exactly what is recorded in the GHC proposal. This mainly breaks IHP programs using forEach, but these can also be manually eta-expanded (and then compile properly)

[hsx| forEach rows renderRow |]  ===> [hsx| forEach rows (\row -> renderRow row)]

Problems with recommended fix

A very unsatisfying "gotcha", needed to remember to eta-expand only calls to forEach rendering Html. I can envision endless beginner questions about this and frustrating experiences for everyone.

Possible solutions?

  • Manually eta-expand calls to forEach
  • (currently implemented in this branch) Remove implicit constraint from Html, resulting in more verbose, but consistent code. Could also ship this change along with a script to manually update all functions returning Html to add an implicit constraint to the beginning of the type signature.
  • Possibly write a type checker plugin? I'm not sure exactly what it would do or how to do it but I'd imagine there's some proof we can give GHC with a plugin to get it to type check our humble Html functions.

Personally, I'd vote for the removal of the constraint from Html. IHP users already understand the need for implicit constraints when writing controller helper functions, and it makes sense that a view that accesses the context needs an implicit constraint for the controller context. I believe this would result in the least amount of cognitive load, and removes a piece of magic in IHP that I think does more harm than good. It wasn't until I was looking into this issue until I understood why we can actually call fromContext in Html views.

thoughts here @mpscholten?

Conclusion

The type checker behavior we relied on before with the Html type synonym could change program semantics and was thus removed from GHC. There are various ways to solve this, each with a sizable set of trade offs. Sad days :(

@mpscholten
Copy link
Member

I'm done moving across the country so I am looking at this again :)

Nice, welcome back! :)

--

I'm not happy with any of these solutions. All of them break existing IHP apps / require a lot of changes when updating.

While this might be expected behaviour in GHC9, IMO we should still file an issue on the GHC GitLab. Maybe there's some other trick we're not aware of. Or alternatively maybe it's possible that GHC automatically eta-expands functions with implicit parameters at the call-site to get back the previous behaviour (the unsound stuff only appears with other type magic, but might not specifically apply to implicit parameters).

If that doesn't lead to anything we can try a TC plugin. If that doesn't work as well, we'll make our implicit parameters explicit as you suggested.

@zacwood9
Copy link
Collaborator Author

zacwood9 commented Mar 4, 2022

Issue has been created: https://gitlab.haskell.org/ghc/ghc/-/issues/21168

@mpscholten
Copy link
Member

Perfect thanks :)

@zacwood9
Copy link
Collaborator Author

zacwood9 commented Mar 27, 2022

Very sad news. After a long, hard battle with the type checker and awful circular dependencies I implemented SPJ's idea of wrapping the constraint in a newtype. It doesn't work...

e.g.

newtype Html = Html ((?context :: ControllerContext) => Html5.Html)

autoRefreshMeta :: Html
autoRefreshMeta = case fromFrozenContext @AutoRefreshState of
        AutoRefreshDisabled -> mempty
        AutoRefreshEnabled { sessionId } ->  [hsx|<meta property="ihp-auto-refresh-id" content={tshow sessionId}/>|]
• Unbound implicit parameter (?context::ControllerContext)
        arising from a use of ‘fromFrozenContext’

in order to construct this value, you need a (?context :: ControllerContext).
Without the context explicitly in the type it just doesn't make any sense.
TC plugin doesn't really make sense either (to me at least) -- fromFrozenContext requires we have a ?context, the TC plugin would have to just lie and change the type

I'm convinced now explicit constraints, with an included auto-fix tool for migrating code, are the way (really the only way) to go. It breaks code, but the breakages are very easy to fix and I should be able to make a script that fixes the code for you. thoughts @mpscholten?

@mpscholten
Copy link
Member

Not happy with this. Then we need more options.

Here's another alternative:

In the past we've been thinking about replacing blaze html in HSX with a custom templating backend. We could make this replacement context-aware.

E.g. imagine this hsx code like this

type Html = ControllerContext -> HtmlStr

view :: Html
view = [hsx|hello {myFunc}|]

would generate a haskell expression à la

view = \context ->
    (htmlStr "hello world")
    <>
    (let ?context = context in (toHtml myFunc))

This would also require an ToHtml instance like this to be composable:

instance ToHtml (ControllerContext -> HtmlStr) where
    toHtml htmlWithoutContext = htmlWithoutContext ?context

@njaremko
Copy link

Is someone working on this custom templating backend? With today's release of HLS supporting 9.2.2, it would be really nice to be able to upgrade to the latest version of GHC and use record dot syntax 🙏

@s0kil
Copy link
Collaborator

s0kil commented Apr 27, 2022

@njaremko Yes, GHC 9 support is coming, maybe even in next release of IHP, As Marc pointed out, we need to make some changes to the HSX generator, so we don't break all the existing IHP apps.

@mpickering
Copy link

newtype Html = Html ((?context :: ControllerContext) => Html5.Html)

autoRefreshMeta :: Html
autoRefreshMeta = case fromFrozenContext @AutoRefreshState of
        AutoRefreshDisabled -> mempty
        AutoRefreshEnabled { sessionId } ->  [hsx|<meta property="ihp-auto-refresh-id" content={tshow sessionId}/>|]

I think you should instead write...

newtype Html = Html { runHtml :: ((?context :: ControllerContext) => Html5.Html) }

autoRefreshMeta :: Html
autoRefreshMeta = Html $ case fromFrozenContext @AutoRefreshState of
        AutoRefreshDisabled -> runHtml mempty
        AutoRefreshEnabled { sessionId } ->  runHtml [hsx|<meta property="ihp-auto-refresh-id" content={tshow sessionId}/>|]

But another good question is why you don't just have

newtype Html = Html { runHtml :: ControllerContext -> Html5.Html }

That would probably be easier to program with.

@zacwood9
Copy link
Collaborator Author

zacwood9 commented Jun 1, 2022

ghc-proposals/ghc-proposals#511 If this is accepted we might be able to get by without any breaking code changes!! Just make that new extension a default and things should work just fine :D

re: @mpickering, sorry for the late response. I agree that's probably the better way to design the API, but I don't see a way to migrate the type to use that without breaking all of the existing code for IHP projects. Some breakage in rare circumstances would be fine, especially since we have GHC guiding us, but for this change, it would be in every single view file.

I really hope the proposal is accepted so we can get IHP on the latest GHCs without needing to break so much code! Thank you in particular for your work and understanding here.

@njaremko
Copy link

GHC 9.2.4 just released with the DeepSubsumption language extension 🎉

@mpscholten
Copy link
Member

I've given the update another try today. As this branch already had several changes on the HSX sides, I've started from scratch / master. The results can be found here: #1487

@mpscholten
Copy link
Member

Closing this as GHC9 is on master already :)

@mpscholten mpscholten closed this Aug 27, 2022
@s0kil s0kil deleted the ghc921 branch August 29, 2022 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants