Open yorickpeterse opened 8 months ago
Another example is the Equal
trait, currently defined like so:
trait Equal[T: Equal[T]] {
fn ==(other: ref T) -> Bool
}
This requires implementations like so:
impl Equal[Array[T]] for Array { ... }
In contrast, with Self
types we can reduce this (back) to:
trait Equal {
fn ==(other: ref Self) -> Bool
}
impl Equal for Array { ... }
This does however introduce a new challenge: if we cast a type to Equal
, we can do x == y
where both x
and y
are typed as Equal
, but the implementations aren't compatible (i.e. x
is actually String
and y
is Float
). To make this safe, we'd have to disallow casting to traits if any of its methods use Self
in their arguments.
I agree with your Clone
example, but are you sure your conclusions about the semantics for Equal
are what you want? It's fairly common in many languages these days to use the ==
operator between two objects that are different types, and would require non-trait based solutions for common situations, such as testing equality between a ByteArray
and Array[Int]
, or ByteArray
and String
.
For example, this would trivially make sense to test equality:
import std.fmt.(fmt)
import std.stdio.STDOUT
class async Main {
fn async main {
let a = [0, 0]
let b = ByteArray.filled(with: 0, times: 2)
STDOUT.new.print(fmt(a == b))
# /tmp/main.inko:9:32 error(invalid-type): expected a value of type 'ref Array[T]', found 'ref ByteArray'
}
}
(I realize that this brings up a larger discussion about the way that inko handles implementing traits on types and that this currently doesn't work for other reasons. Attempting to implement Equal
a second time for Array
(say, impl Equal[ByteArray] for Array
) fails with the error that the trait Equal
is already implemented for Array
. I would also recommend changing that behavior, but that's probably a separate ticket.)
The way that Rust handles this is by using the Self
trait as a default value for the type parameter -- trait PartialEq<Rhs = Self>
-- allowing you to implement impl PartialEq for T
. Is there a reason that sort of feature wouldn't be the right direction to consider?
@klieth To support what you're referring to, we'd need to add support for overloading methods based on the traits they originate from. This is something I want to avoid due to the complexity it brings with it.
Similarly, default type parameter values is something I also want to avoid, again to keep the compiler and type system complexity at a level that I'm comfortable with.
At some point in the future that may change, but it won't be the case for at least a few more years (if ever).
Description
Inko used to have support for
Self
types, but this was removed due to the complexity and bugs this introduced to the type system. This change however makes certain patterns difficult or even impossible to implement. The traitstd.clone.Clone
is a good example of this. Ideally, the trait is defined as follows:This way if you use e.g.
String.clone
orFoo.clone
you get aString
orFoo
back. This currently isn't possible, so we have to use generics instead:In this particular case it works, but results in a bit of type boilerplate/redundancy:
Versus just the following:
While this case isn't too difficult, in other cases it can get really messy, requiring extensive workarounds.
I would like to explore options to reintroduce
Self
types in some reduced capacity, ideally without having to consider them in every place a type may occur. This means having to restrictSelf
types to trait definitions, such that we can more easily substitute them with real types.Related work
No response