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]
