All classes as data types, even single-methods ones?
Motivation
Currently, single-method classes (more precisely single-method-or-superclass) are implemented as newtypes
’s around that method; see Note [Single-method classes]
. Roughly, where we have
class C a where { op :: a -> a }
we implement the class as
newtype C a = MkC (a->a)
The benefit of the status quo is that these classes are cheaper at run time (no need to project out of a dictionary; less allocation when dictinaries aren’t static anyways).
But there are various downsides:
-
We are strongly considering making
Type
andConstraint
always and everywhere distinct. But the newtype axiom for the classC
would beax :: C a ~ a->a
; and henceKindCo ax :: Constraint ~ Type
, which is a bit like having an axiomInt ~ Bool
-- chaos would result. Not using newtypes for classes would unblock this road, with many desirable consequences. -
Having newtype classes leads to special cases in GHC, newtype-backed classes and data-backed classes need to be handled differently. (This still needs to be quantified.)
-
RULES on class methods don't work well with new-type backed classes. This is tracked in this note; the gist is that (currently) class ops and dfuns of newtype-packed classes inline early and aggressively, to expose their cheap definition (it’s just a cast after all), and this gets in the way of matching RULES against class ops and dfuns.
This issue can likely be fixed with reasonable amount of extra work (delaying inlining etc.), but if classes were uniform there would be no need to figure this out.
-
The
-fdicts-strict
flags currently has to exclude newtype-backed classes, because functions that are strict in the dict should not be strict in the method. #17758 (closed) proposes to make-fdicts-strict
by default, and this would make things more uniform again. -
Currently classes cannot have unlifted methods, mainly (we think) because that wouldn't work for newtype classes. We could lift this restriction.
-
#21229 (closed) says we should not inline DFuns. But it is is jolly hard not to inline newtype DFuns (!7788 (closed)). NB: #21229 (closed) is only closed because we reverted an earlier (otherwise highly desirable), and opened #21470 (closed).
Proposal
So in [this comment](#20535 (comment 385765)_ Simon PJ suggests Plan A, that maybe it’s better to drop this special-casing and compile all classes alike. I’m opening this issue as a place to discuss this, independent of the problem in #20535 (which may have other solutions).
This ticket summarises work on this idea.
If performance is affected too much, an alternative Plan B could be:
- Make single-method classes look like vanilla data types, with a single, unary data constructor, right up to the code generator
- But compile
(MkC x)
asx
, andcase c of MkC x -> blah
asblah[c/x]
; that is discard the boxing and unboxing - The Simplifier would have to behave as now, with special cases for single-method classes, since they would not really be lifted after all.
I prefer Plan A because it's simpler, if the perf implications are acceptable.
Draft MR: !7278
Worries
Why not do this?
- Performance. Maybe there are cases where not having that extra box for a single-method class is super-important.
- The
reflection
library makes cunning use (viaunsafeCoerce
) of the (undocumented) fact that single-method classes are implemented with newtypes. Fortunately, our newwithDict
story allowsreflection
not to rely on such undocumented accidents, nor onunsafeCoerce
. See #21568 for our plan.