google / google/codeworld

Replace codeworld-base `==` with GHC source plugins

Open
#687 0 comments 0 reactions 0 assignees View on GitHub
discussion
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.