Special treatment of `CanEqual` in context bounds

Hey!

I’ve noticed an issue when working with strictEquality: it’s impossible to use it with context bound syntax. For example, def foo[A: CanEqual] doesn’t work because CanEqual has two type parameters, whereas the context bound syntax only works for types with one. By contrast the derives feature does work with CanEqual, as the compiler treats it as a special case.

I propose to treat CanEqual specially in context bounds as well. [A: CanEqual] should translate to (using CanEqual[A, A]) rather than (using CanEqual[A]), as the latter is a type error. I would like to see a future where more and more people switch on strictEquality, and this will only happen if using it is as smooth and hassle-free as possible.

1 Like

Couldn’t this be solved more easily with an alias instead of adding a special case?

object CanEqual {
  type Self[A] = CanEqual[A, A]
}

def foo[A: CanEqual.Self]

Sure, but it’s less convenient and one more thing to remember and learn about. CanEqual is already treated specially by the language, so I think it makes sense to go all the way.

I’m not sure I agree with “one more thing to remember”. For me, knowing what context bounds are and knowing what the CanEqual interface looks like, I would assume that it cannot be used as a context bound and so I would not try to use it like that. Except maybe by accident, after which I would be surprised that it worked and have to look up why. So for me the special case would be “one more thing to remember”.

That’s a good argument. Though personally I would have preferred if they had applied the “less special cases” principle from the start. But if the cat’s already out of the bag might as well go all the way I guess…

3 Likes

Instead of introducing special-case logic for a specific type class like CanEqual, I propose a generalization of the context bound expansion rule to automatically handle any type constructor based on its arity (number of type parameters). This approach provides a consistent, simple solution for self-bounded type classes without sacrificing the clarity of the [T: C] syntax.

Current Rule: f[T: C] => f[T](using C[T])

Proposed Rule: f[T: C] => f[T](using C[T, T, .., T]) where T appears N times, and N is the exact number of type parameters required by C.

This generalization elegantly solves the CanEqual issue while maintaining backward compatibility for unary type classes, relying on the fact that Scala disallows type constructor “overloading” (different arities for the same name).

2 Likes

Hi @Eastsun,

can you name other examples where that would be useful?

2 Likes