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

Use case: restricting GAT input parameters #16

Open
AndreiCravtov opened this issue Feb 24, 2025 · 0 comments
Open

Use case: restricting GAT input parameters #16

AndreiCravtov opened this issue Feb 24, 2025 · 0 comments

Comments

@AndreiCravtov
Copy link

AndreiCravtov commented Feb 24, 2025

Currently, GATs are rather inflexible when it comes to their input parameters (especially if those are types) so you might have something like this:

trait Family {
  type Of<T>;
}

struct OptionFamily;

impl Family for OptionFamily {
  type Of<T> = Option<T>;
}

This is a trait intended to be implemented by "type constructor" types like OptionFamily, but notice it doesn't easily extend to other types. What if a type constructor has additional constraints?

enum Maybe<T: Clone> {
  Nothing,
  Just(T)
}

trait CloneFamily {
  type Of<T: Clone>;
}

struct MaybeFamily;

impl CloneFamily for MaybeFamily {
  type Of<T> = Maybe<T>;
}

We can see that MaybeFamily could never implement Family because of the generic type constraints, so we have to create a whole separate trait to accommodate for this. And we have to do this for every new complex type constraint, duplicating all the logic built on Family (e.g. maybe we built a Family>Functor>Applicative>Monad>... hierarchy and now we need to have CloneFamily>CloneFunctor>CloneApplicative>CloneMonad>... parallel hierarchy.)
Associated traits could be the perfect solution here, as a way to introduce restrictions to the GAT input parameter, and reuse the trait for all sorts of types:

trait Family {
  trait Bounds = ?Sized; // default-associated-type syntax for traits, 
                         // `?Sized` used to mean "no bounds" but could
                         // use `()` unit trait if introduced as a concept
  type Of<T: Self::Bounds>;
}

struct OptionFamily;

impl Family for OptionFamily {
  trait Bounds = Sized;
  type Of<T> = Option<T>;
}

struct MaybeFamily;

impl Family for OptionFamily {
  trait Bounds = Clone;
  type Of<T: Clone> = Maybe<T>;
}

This, together with non-lifetime binders like ... where for<T: Foo> Bar<T>: Baz<T>, ... would make GATs a whole lot more easy to work with, especially when expressing complex constraints.

In Haskell, this concept is used by libraries like rmonad to define restricted RMonad typeclasses to enable datatypes such as Set to be made into monads.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant