Replace codeworld-base `==` with GHC source plugins
- Dominant language
- Haskell
- Stars
- 1.3k
- Forks
- 201
- PR merge metrics
- No merged PRs in 30d
Description
This has to wait on GHCJS to catch up to 8.6, but I'm creating this issue to document the plan.
## The current state
`codeworld-base` implements a polymorphic `(==) :: a -> a -> Bool` using some JavaScript that @luite wrote, that walks the GHCJS heap, and compares for structural equality. This works, but it has a few drawbacks.
1. I worry about performance, since comparisons happen a lot. (There are some rewrite rules to revert to Haskell's `==` for common types, but they might not be firing.)
2. The main problem is that it ties `codeworld-base` to GHCJS. Code written with `codeworld-api` can now be compiled natively using `blank-canvas`, but this doesn't work for `codeworld-base`. That, in turn, means that we can only do mobile applications via a JavaScript wrapper, which is likely to be too slow on older phones. And if https://github.com/WebGHC/ghc ever gets off the ground, we'll be blocked from migrating there, as well, since the heap won't be so easily introspected.
## The long-term plan
We may be able to switch back to Haskell's `==` operator, if we can solve two problems. First, how do we let a student compare values of their own types without deriving `Eq` instances? And second, how do we let users write explicit type annotations without specifying constraints when they use polymorphic equality?
The first problem can be solved by making sure that appropriate `Eq` instances exist when they are needed, and add them to type declarations. This is easy to do with types declared in `codeworld-base`. It appears we can now write a GHC source plugin that will automatically derive `Eq` for student-defined types.
The second problem can be solved by rewriting type annotations to always include an [extra constraints wildcard](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#extra-constraints-wildcard). It appears this can also be done in a GHC source plugin.
Some related bookeeping:
* We need `-XPartialTypeSignatures -Wno-partial-type-signatures` to avoid warnings or errors for the partial type signatures. It's vaguely disappointing that we can't get warnings for user-specified partial types. (I definitely don't want warnings to bug students before they learn about type annotations, but I enjoy being able to tell them to just write `foo :: _` to ask the compiler what type something has.) But it's a minor loss.
* When displaying the `codeworld-base` documentation in the guide, we'd need to hide `Eq` constraints, the same way we already hide `HasCallStack`.
* We'd need to rewrite error messages for missing `Eq` instances with something more friendly, like "Functions, pictures, or types that contain them, cannot be compared." (`Picture`s are included because they have a semantics as `Point -> Color` which doesn't agree with any definable `Eq` instance.) Or, probably even better, just declare an `Eq` instance for functions and `Picture`s that fails at runtime. That should then make pretty much anything comparable that can be written with just `codeworld-base`. (except extremely large tuples, I guess... but we can get large enough.)
If this is done for `Eq`, we could also revisit `Ord` and `Show`, adopting the same approach there. `Picture` and function values should have `Show` instances that just print "\<\\>" or "\<\\>", rather than failing, and infinite (or long) lists should automatically ellipsize. It's really exciting to be able to print state values, which usually have user-defined types, when debugging!)
## The short-term option
After thinking this through, I wonder how often `codeworld-base` users really compare equality of user-defined types or write explicit type signatures where they need polymorphic equality, anyway. I imagine it's not very common. It might be feasible to migrate `Eq` *now*, breaking the rare program that does one of these things.
I suspect it's a bit more common for user programs to compare `Picture` or function values, which "works" now, even though the results are semantically wrong. I'm okay with breaking these (already broken) programs. But I do wish we could warn these students for a bit first.
It might be possible to fake warnings from the JavaScript implementation of `==`, before switching.
Or, after switching, https://ghc.haskell.org/trac/ghc/ticket/12014 seems to propose deprecating instances, but it appears it was not implemented.
Contributor guide
Assessment
This issue has not been assessed yet.