Facet relationship between parameterized types (#900)

This commit is contained in:
josh11b
2021-10-19 13:57:29 -07:00
committed by GitHub
parent cfb36b85e5
commit dc9c36660b
+30 -14
View File
@@ -1398,23 +1398,34 @@ Now consider a type with a generic type parameter, like a hash map type:
```
interface Hashable { ... }
class HashMap(KeyT:! Hashable, ValueT:! Type) { ... }
class HashMap(KeyT:! Hashable, ValueT:! Type) {
fn Find[me:Self](key: KeyT) -> Optional(ValueT);
// ...
}
```
If we write something like `HashMap(String, i32)` the type we actually get is:
A user of this type will provide specific values for the key and value types:
```
HashMap(String as Hashable, i32 as Type)
var hm: HashMap(String, i32) = ...;
var result: Optional(i32) = hm.Find("Needle");
```
This is the same type we will get if we pass in some other facet types in, so
all of these types are equal:
Since the `Find` function is generic, it can only use the capabilities that
`HashMap` requires of `KeyT` and `ValueT`. This implies that the
_implementation_ of `HashMap(String, i32).Find` and
`HashMap(String as Hashable, i32).Find` are the same. In fact, we could
substitute any facet of `String`, and `Find` would still use
`String as Hashable` in its implementation. So these types:
- `HashMap(String, i32)`
- `HashMap(String as Hashable, i32 as Type)`
- `HashMap((String as Printable) as Hashable, i32)`
- `HashMap(String as Printable, i32)`
- `HashMap((String as Printable & Hashable) as Hashable, i32)`
are also facets of each other, and Carbon can freely allow casts and implicit
conversions between them.
This means we don't generally need to worry about getting the wrong facet type
as the argument for a generic type. This means we don't get type mismatches when
calling functions as in this example, where the type parameters have different
@@ -1425,10 +1436,16 @@ fn PrintValue
[KeyT:! Printable & Hashable, ValueT:! Printable]
(map: HashMap(KeyT, ValueT), key: KeyT) { ... }
var m: HashMap(String, i32);
var m: HashMap(String, i32) = ...;
PrintValue(m, "key");
```
However, those types are still different. A caller of `Find` observes that its
signature reflects the actual type parameters passed to `HashMap`, not their
projection onto the `Hashable` or `Type` facets. In particular, the return type
of `hm.Find` is `Optional(i32)`, not `Optional(i32 as Type)`. (Incidentally,
`Optional(i32)` and `Optional(i32 as Type)` are also facets of each other.)
## Adapting types
Since interfaces may only be implemented for a type once, and we limit where
@@ -1574,13 +1591,12 @@ one difference between them is that `Song as Hashable` may be implicitly
converted to `Song`, which implements interface `Printable`, and
`PlayableSong as Hashable` may be implicilty converted to `PlayableSong`, which
implements interface `Media`. This means that it is safe to convert between
`HashMap(Song, i32) == HashMap(Song as Hashable, i32)` and
`HashMap(PlayableSong, i32) == HashMap(PlayableSong as Hashable, i32)` (though
maybe only with an explicit cast) but
`HashMap(SongHashedByTitle, i32) == HashMap(SongHashByTitle as Hashable, i32)`
is incompatible. This is a relief, because we know that in practice the
invariants of a `HashMap` implementation rely on the hashing function staying
the same.
`HashMap(Song, i32)` and `HashMap(PlayableSong, i32)` (though maybe only with an
explicit cast), since the implementation of all the methods will use the same
implementation of the `Hashable` interface. But
`HashMap(SongHashedByTitle, i32)` is incompatible. This is a relief, because we
know that in practice the invariants of a `HashMap` implementation rely on the
hashing function staying the same.
### Extending adapter