It's certainly a question that it's important to know the answer to,
for my purposes!  Given that one can *already* define a type class
method whose value is a product type containing one or more function
types then I hope GHC's optimizer is already capable of doing the
necessary work there!

Tom

On Thu, Dec 11, 2025 at 09:19:37PM +0800, Gergő Érdi wrote:
> My question is, if you do this thoroughly and end up with plain old
> functions getting plain old record parameters containing the methods, is
> GHC capable of seeing through enough calls to figure out that all these
> function arguments are actually fully determined by typeclass instances,
> and thus by types, and thus they can be eliminated based on the types alone?
> 
> Maybe it is, this is a genuine question, although I can see how my original
> reply reads like an implicit "Surely," 😀
> 
> On Thu, Dec 11, 2025, 21:14 Tom Ellis <
> [email protected]> wrote:
> 
> > On Thu, Dec 11, 2025 at 09:07:28PM +0800, Gergő Érdi wrote:
> > > Doesn't this undermine a lot of the specialization-based optimizations to
> > > get rid of runtime dictionary passing?
> >
> > If it does then my idea is dead on arrival for anything performance
> > sensitive. But why would it?  I asked the question because I was
> > concerned that classes with a single method containing a dictionary
> > might have performance overhead compared to "unpacking the dictionary"
> > in the class body, i.e. the difference between Simon's
> >
> >     class C a where { op1, op2 :: a -> a }
> >
> > and my
> >
> >     data CD a = MkCD { op1Impl :: a -> a, op2Impl :: a -> a }
> >     class C a where cImpl :: CD a
> >
> > Simon says there is no overhead. What specialization-based
> > optimizations are you thinking of that may not apply to my version?
> >
> > Tom
> >
> > > On Thu, Dec 11, 2025, 21:01 Tom Ellis <
> > > [email protected]> wrote:
> > >
> > > > On Thu, Dec 11, 2025 at 09:15:16AM +0000, Simon Peyton Jones wrote:
> > > > > Classes with exactly one method and no superclass (or one superclass
> > and
> > > > no
> > > > > method) are called "unary classes".  And yes, they are still
> > implemented
> > > > > with no overhead.
> > > > >
> > > > > See this long Note:
> > > > >
> > > >
> > https://gitlab.haskell.org/ghc/ghc/-/blame/master/compiler/GHC/Core/TyCon.hs#L1453
> > > >
> > > > Super, thank you for the reference.
> > > >
> > > > > > I find type classes very difficult to evolve in a way that
> > > > > > satisfies my stability needs. Part of the reason for this is that
> > > > > > type classes as typically used don't really permit any form of
> > > > > > data abstraction: you list out all the methods explicitly in the
> > > > > > class definition.  There is no data hiding.
> > > > >
> > > > > That's odd.   Can't you say
> > > > > ```
> > > > > module M( C, warble ) where
> > > > >    class C a where { op1, op2 :: a -> a }
> > > > >
> > > > > warble :: C a => a -> a
> > > > > warble = ...
> > > > > ```
> > > > > and now a client of `M` can see `C` and `warble` but has no idea of
> > the
> > > > > methods.
> > > >
> > > > That deals with one direction across the abstraction boundary: the
> > > > elimination form.  We also need introduction forms as you point out:
> > > >
> > > > > Of course if a client wants to make a new data type T into an
> > instance
> > > > of C
> > > > > then they need to know the methods, but that's reasonable: to make T
> > an
> > > > > instance of C we must provide a witness for `op1` and `op2`.  So your
> > > > > teaser is indeed teasing.
> > > >
> > > > Right, and once witnesses have been provided for `op1` and `op2`, the
> > > > client is now coupled to that interface.  Here's what I'm suggesting
> > > > instead:
> > > >
> > > >     -- | Crucially, CD is abstract
> > > >     module M( C, CD, op1, op2, warble, Ops(..), cdOfOps ) where
> > > >
> > > >     data CD a = MkCD { op1Impl :: a -> a, op2Impl :: a -> a }
> > > >
> > > >     class C a where cImpl :: CD a
> > > >
> > > >     warble :: C a => a -> a
> > > >     warble = ...
> > > >
> > > >     op1 :: C a => a -> a
> > > >     op1 = op1Impl cImpl
> > > >
> > > >     op2 :: C a => a -> a
> > > >     op2 = op2Impl cImpl
> > > >
> > > >     data Ops a = MkOps { opsOp1 :: a -> a, opsOp2 :: a -> a }
> > > >
> > > >     cdOfOps :: Ops a  -> CD a
> > > >     cdOfOps ops = MkCD { op1Impl = opsOp1 ops, op2Impl = opsOp2 ops }
> > > >
> > > > And clients can now define
> > > >
> > > >     instance C T where
> > > >       cImpl = cdOfOps MkOps { opsOp1 = ..., opsOp2 = ... }
> > > >
> > > > But I can also provide more helper functions such as these:
> > > >
> > > >   cdOfId :: CD a
> > > >   cdOfId = MkCD {op1Impl = id, op2Impl = id}
> > > >
> > > >   cdOfTwice :: (a -> a) -> CD a
> > > >   cdOfTwice f = MkCD {op1Impl = f, op2Impl = f . f}
> > > >
> > > > So instances can be written briefly, in a way that is typically done
> > > > with DerivingVia:
> > > >
> > > >   instance C T2 where
> > > >     cImpl = cdOfId
> > > >
> > > >   instance C Bool where
> > > >     cImpl = cdOfTwice not
> > > >
> > > > Why do this? Suppose I realise that it is a law that `op2` must
> > > > *always* be `op1 . op1`.  Then `cdOfOps` becomes risky, and I can add
> > > > a warning to it, deprecate it, and subsequently remove it if I want.
> > > > Everything else, including `cdOfId` and `cdOfTwice` are safe, and can
> > > > remain unchanged.
> > > >
> > > > There is no easy path if `op2` is a method.  I can't add a warning to
> > > > it, because it's still safe to *use* it and client code will be using
> > > > it.  It's just unsafe to *define* it.  Ideally it should be lifted out
> > > > of the class definition and defined as `op2 = op1 . op1`, but that
> > > > breaks every client who has a C instance defined, without the ability
> > > > to provide a smooth deprecation cycle.
> > > >
> > > > Anyway, I hope to be able to write this up in more detail in the near
> > > > future, including the benefits I see we would have had during AMP,
> > > > Monad Of No Return, and the proposal to remove (>>) from Monad, if
> > > > this approach had been standard practice.
> > _______________________________________________
> > ghc-devs mailing list -- [email protected]
> > To unsubscribe send an email to [email protected]
> >

> _______________________________________________
> ghc-devs mailing list -- [email protected]
> To unsubscribe send an email to [email protected]

_______________________________________________
ghc-devs mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to