Define the relationship between a type and an interface it satisfies
Closed, ResolvedPublic

Description

In Go, types may implicitly satisfy interfaces. For example, given an interface

type Fooer interface { Foo() }

a concrete type such as

type T int
func (T) Foo() {}

will satisfy Fooer, so that this is legal:

func Invoke(f Fooer) { f.Foo() }
...
Invoke(T(0))

The question is: What relationship should hold in the Kythe graph between T and Fooer?

Arguably the answer is "none", since there may be no way for the compiler to discover all the interfaces that T satisfies without a global scan of all available code. In practice, however, it is often useful to record the relationship T has to known interfaces it satisfies. So for example, if T is defined in package p, it would be reasonable to associate T with all the interfaces declared in p that it satisfies: This we can easily determine at indexing time. In fact, we can extend this to (top-level) interfaces declared in any package d that p refers to, because we already have to have their type information in order to compile.

When the relationship is explicit, we use extends, as in

class A implements B {}  // A extends B in the Kythe graph

Do we want to do the same thing for the implicit-satisfaction case? It feels like there is an important difference here, but I'm not sure how to characterize the difference. So the questions to answer here are:

  1. Is this different enough that it deserves its own edge kind?
  2. If so, what should that edge be called, and what should it mean?
fromberger created this task.Via WebFri, Mar 10, 5:05 PM
fromberger added a project: Schema.
fromberger added a subscriber: fromberger.
Herald added a subscriber: Core Team. · View Herald TranscriptVia HeraldFri, Mar 10, 5:05 PM
fromberger added a comment.EditedVia WebMon, Mar 13, 5:09 PM

[edit: fixed typos]

After thinking about it some more, I think this might be worth adding a new implements edge kind for (or perhaps satisfies, though I mildly prefer implements on purely aesthetic grounds). My rationale is that they answer slightly different questions. Continuing with Go for example, given

type Alpha interface { Foo() }
type Bravo interface { Alpha ; Bar() }

we would say that Bravo extends Alpha because by construction any type that satisfies the former also satisfies the latter. Moreover, that property has to remain true unless the declarations of these types change. In other words: The "extends" relationship is explicitly required by the programmer, and changing the contents of Alpha, or the relationship between Alpha and Bravo, may require all implementations of Bravo to be updated.

By contrast if I write:

type S struct{}
func (S) Foo() {}
func (S) Bar() {}

then S implicitly satisfies both Alpha and Bravo, but absent some other use of the type that constraints it further (e.g., code that passes an S to a function taking Bravo), it's not required to satisfy either, and changing Bar to Baz would not stop the program from compiling. Perhaps more importantly: If I change the definition of S it has no effect on any other implementations. And while changing Alpha may require us to update S if we want that association, it has no pass-along effects.

Is this difference significant enough to deserve its own label? I'm moderately convinced it is, but would welcome other viewpoints.

fromberger closed this task as "Resolved".Via WebThu, Mar 23, 1:59 PM
fromberger claimed this task.

Conclusions:

  1. It is worth modeling, and should be separate from extends (mostly on the grounds that incompleteness is valuable signal).
  2. The name satisfies works and is better than implements because the latter is overloaded by other languages more.

Add Comment