Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feedback: Self type member makes mixin givens less compositional #20366

Open
propensive opened this issue May 8, 2024 · 0 comments
Open

Feedback: Self type member makes mixin givens less compositional #20366

propensive opened this issue May 8, 2024 · 0 comments
Labels
stat:needs triage Every issue needs to have an "area" and "itype" label

Comments

@propensive
Copy link
Contributor

propensive commented May 8, 2024

Compiler version

Latest main branch, 3393f7ea7704eb1dd553c96a805906821c61db5f.

Feedback

This is some specific feedback from the first day of experimenting with the newly-merged modularity feature on an existing codebase of over 2000 given definitions. Wrt to the new typeclass encoding, the documentation says,

Allows to create aggregate type classes that combine givens via intersection types.
[...]
This makes Self-based designs inherently more compositional than parameterized ones.

which is true in many cases, but only when composing typeclass instances whose Self members have the same type. That is probably the majority of cases where there's justification for a single given to implement multiple typeclass interfaces, but it's not all.

Here's a concrete example of an exceptional case. I want to use two typeclasses to generalize time, with a typeclass, GenericInstant representing instants in time, and another, GenericDuration, representing durations. (Their abstract methods have different names, of course.)

In the old style, I can create instances such as,

given myTimeApi:  (GenericInstant[MyInstant] & GenericDuration[MyDuration]) = ...

and with a single import of myTimeApi, a user can bring into scope a single instance which is a GenericInstant and a GenericDuration, and which are self-consistent. That is to say, the hypothetical MyInstant and MyDuration types are designed to work harmoniously together. So the single-import is important for making it easy to use a family of types together. Put another way, I don't want to make it easy for users to pick potentially-incompatible types arbitrarily. (I believe this is the same motivation for the compositionality point from the documentation.)

I have at least one other example of this relating to generic filesystem types (paths, files, directories).

A further issue

I've noticed a related issue while converting my existing typeclass definitions. I got into the habit of giving all my type parameters meaningful names (rather than just T) a while ago, which means that I can deduce the purpose of a type parameter or member easily from its name, using only very local reasoning. Instead, I'm now seeing the type Self far too often, and it's not as immediately clear what it refers to. In more complex typeclasses (multiple parameters or many definitions) I have to look at the trait signature to deduce what the Self type represents, or infer its meaning from other places I see it used. This isn't an improvement.

The name Self itself is fine for what it does, in general. But it only describes the privileged status of that type member with respect to using its enclosing typeclass in a context bound, and at the expense of it losing all other meaning. I might get used to this after spending longer with it, but if not, I'm likely to just add an extra type alias to every typeclass trait to ascribe Self a more meaningful name.

A possible solution

I think that any mitigation of these issues would need to grant the freedom to choose the "Self" type's name, and other changes would follow to accommodate that. It's obviously not such a simple solution, but here's one suggestion for how that might work:

  1. introduce a soft modifier to type, say context (or anything else), to mark a type member as "privileged" in context bounds
  2. make the is definition magic so that it injects its left operand into the context type of the right operand
  3. restrict context bounds types to typeclasses with exactly one context type
  4. warn if more than one context type is defined in the same template

I don't see a problem with multiple context types coexisting in the same template, as would be the case in a given instance of GenericInstant & GenericDuration. It would just be pointless to define them in the same body.

@propensive propensive added the stat:needs triage Every issue needs to have an "area" and "itype" label label May 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stat:needs triage Every issue needs to have an "area" and "itype" label
Projects
None yet
Development

No branches or pull requests

1 participant