> type Reader interface {
> Get() interface{}
> Active() bool
> }
>
> type Writer interface {
> Put(interface{})
> Active() bool
> }
>
> It is straightforward to develop an implementation that has a Get(), a
> Put(interface{}) as well as an Active() method. So, why can't I intersect
> both types by including them in a ReaderWriter interface type like this:
>
> type ReaderWriter interface {
> Reader
> Writer
> }
>
> This limitation is really annoying as it limits reuse of interface types
> that have more than one method significantly. Is there any reason why the
> language doesn't allow me to do this?
The reason is IMO the obvious ambiguity of an method that materializes
when composing an interface inheriting two methods of the same
signature and name. It could be probably solved by qualifying the
ambiguous method by e.g. the name of the parent entity it comes from.
Then one would have an language orthogonality exception in that the
pkgname.Exported pattern is no more universal to the reader in the
sense of a reference to Exported as a top level exported declaration
found in pkgname. Such exceptions are - I guess - unwelcome by the
language designers.
The problem is not ambiguity. The problem is duplication. The error
given by the compiler is very clear: "duplicate method Active".
This makes sense, because the specification says: "The effect [of
embedding an interface type T] is equivalent to enumerating the
methods of T explicitly in the interface.". So, it is equivalent to:
type ReaderWriter interface {
Get() interface{}
Active() bool
Put(interface{})
Active() bool
}
which, indeed, produces the same error.
You are asking for a language change, but I'm not sure how that change
would look like.
What if you add a new interface to your example?
type Activer interface {
Active() bool
}
type ReaderWriter interface {
Reader
Writer
Activer
}
Where is the limit?
Interfaces would not be so powerful if they were not so simple. Maybe
limiting the use of complex interfaces is not such a bad thing.
--
- yiyus || JGL .
You haven't shown any problem so far which would answer the OP
question, and I don't think you'll be able to. There's no ambiguity.
The correct answer to his question is most probably "it's simple
to implement the way it is now.".
--
Gustavo Niemeyer
http://niemeyer.net
http://niemeyer.net/blog
http://niemeyer.net/twitter
No, that's good news for you. You're about to understand how Go
interfaces work. :-)
> No ambiguity?
No, no ambiguity. You can obviously do this today:
type A interface { String() string }
type B interface { String() string }
type C interface { String() strign }
All the OP is asking is why he can't do:
type C interface { A; B }
with exactly the same semantics as the previous version.
Stop for a moment and read what everyone in this thread is trying to tell you.
i think niemeyer is right.
there's no ambiguity in the original. an interface holds a *set*
of methods, and there's no reason that the result of the original
post's ReaderWriter interface couldn't be the set union of the
Reader and Writer interfaces (with an error if Active had a different
type in each). that would be a way to implement it unambiguously.
however the current definition is simple and sufficient for
most purposes. interface embedding is only a convenience
anyway - you can always explicitly include the methods
you want if necessary.
> This limitation is really annoying as it limits reuse of interface types
> that have more than one method significantly. Is there any reason why the
> language doesn't allow me to do this?
While this sounds somewhat like a reasonable request, I think there's
a detail there which makes it not so desirable.
The fact that you have a common interface between two methods means
there's another implicit interface which is not being declared, but
which exists. What would people do if they wanted to execute the
Active() method in either type, for instance?
The solution is likely to have a third interface:
type Activable interface {
Active() bool
}
and share embed this interface instead.
tmp.go:34: cannot use struct literal (type RW) as type I in
assignment:
RW does not implement I (missing Active method)
We're not talking about embedded method resolution. We're talking about composing interfaces, which is something entirely different.
On Sat, Feb 19, 2011 at 11:12 AM, Steven <stev...@gmail.com> wrote:
> A concern might be that two methods with identical signatures might have
> entirely different meanings:
> type Cowboy interface { Draw() }
> type Canvas interface { Draw() }
> type MyInterface interface {
> Cowboy
> Canvas
> }
> Of course, it isn't likely that these two methods would have identical
> signatures anyway[...]
And in fact, given code like this (identical signatures with different
meanings), while the current language prevents you from declaring that
something is both a Cowboy and a Canvas, it still allows Cowboys to be
implicitly converted into Canvasses. Which emphasizes that when
you've got identical method signatures with differing meanings, you're
already in a very dangerous position.
[...]
> choosing which doc comment to use. One option would just be to mark each
> comment as originating with a specific embedded interface. Another would be,
> if the methods were ultimately originating from the same source, ie:
> type Sheriff interface { Cowboy; Arrest() }
> type Bandit interface { Cowboy; Raid() }
> type BanditSheriff interface { Bandit; Sheriff }
> then there isn't an issue either.
I'll just point out that this (as I see it) is the common case that
really cries out for this to be legal. It's frustrating that you
can't compose together two interfaces that each involve the same
interface: these are conceptually the *most* composable, in the sense
that they are already known to be the "same sort of thing". Okay, it
doesn't make sense that someone would be both bandit and sheriff... at
least by the rule of law... although it does happen in real life.
--
David Roundy
This isn't strictly speaking true. An interface defines a query regarding the behaviour of an object (i.e. is the compiled form of a duck-typing message send query which reflects the occasional coupling between messages in a given context) and as such ignores the degree in which several objects returned by that query differ. This is the flipside of a struct or other type where the focus is on full structural equivalence. Two points will always be structurally equivalent regardless of whether they exist at different locations in a plane, however two riders may have no structural elements in common at all.
So whilst it's certainly the case that any object fulfilling an interface can be guaranteed to respond to a given message this is not the same as these objects all being of the same type, rather there is a type isomorphism which is being captured by the interface but which could be considered a meta-type as there are problem domains in which an object might well move from one meta-type to another during its lifetime without necessarily changing its essential structural type.
The latter incidentally are the edge cases which duck-typed languages excel at. Solving similar problems in statically typed languages generally involves an incestuous relationship with a given runtime which is a nightmare to maintain (although a lot of fun to figure out).
A change to the specification of interfaces which allowed duplication of methods across several different interfaces might seem like a good idea on the surface, but in reality it's creating a wrapper that embodies *more than one type isomorphism* and whilst this might in some cases be desirable, in the general case it will lead to more complicated interfaces which become difficult to reason about as the number of compositions increases. Putting the smarts for that into the compiler may not even be possible even if it were desirable.
And anyway, duck-typing is at its most productive when it's applied with a light touch. At least I've found that the case during my years of Ruby coding.
Currently I find one of the best way to reason about interfaces (and duck-typing in general) is to lift the analogous case of measurement in quantum mechanics. There is a finite degree of information which any measurement will return, and that information is tightly coupled to both a particular property and the context in which the measurement is made. The more you weaken that restriction the less benefit you gain, both in terms of cost of performing the measurement (a reasonable consideration in a systems language) and in terms of the design flexibility which duck-typing provides.
As you rightly mention regarding your Cowboy and Canvas example, the muddle arises not from the underlying abstractions but from the semantics which have been used to describe them. Each interface in this case implements a different message, and that message should actually be parametric as each describes an action being performed on the object by an outside agency, or an action which the object is performing.
type Cowboy interface { Draw(w Weapon) }
type Canvas interface { Draw(l Line) }
type MyInterface interface {
Cowboy
Canvas
}
This would be a perfectly valid conceptual description (though invalid Go code) for a Cowboy who happened to also be a possessor of Body Art, but then again perhaps it would make more sense in that case for Canvas to be an embedded structural type, probably as a field called Skin, and for the method to actually be called DrawUpon to disambiguate it from DrawWith (for a pen or pencil) and Draw (for a firearm). That could still lead to ambiguity with a LoanAgreement where DrawUpon means something different, but that's the point of bounding all of these things by context.
Your other example also contains some very messy abstractions and should really be:
type Cowboy interface { Draw(w Weapon) }
type Sheriff interface { Arrest(m Miscreant) }
type Bandit interface { Raid(p Place) }
Where we would only ever be interested in whether our subject is a member of one of these duck-typed groups when attempting to perform the appropriate action. It is enough to know a Sheriff can arrest a miscreant to know whether or not it is type safe for the Sheriff to attempt to.
Of course it might be nice to know if the subject happened to belong to all three groupings at which point we can use a function to find out:
func IsCowboyBanditSheriff(i interface{}) bool {
if _, ok := i.(Cowboy); !ok { return false }
if _, ok := i.(Bandit); !ok { return false }
if _, ok := i.(Sheriff); !ok { return false }
return true
}
However such queries are generally of limited value in real-world systems which use duck-typing, as demonstrated by how rarely in message-passing languages you see code that relies on an explicit chain of responds_to? (or equivalent) method calls. It's much cheaper and less verbose to send the message/make the assertion at the time it's required and only worry about the overlaps for purely accounting purposes. Likewise where a message isn't supported, the runtime code location is nearly always the place that should be being dealt with. It may be an error, or it may just be a reason to exclude a given object from being processed at a given time.
What I'm essentially saying is that interfaces are not a tool for type composition, but rather for type decomposition. By designing them well you minimise the dependencies between structural types whilst maximising their reuse. The normal rules of low-coupling/high-cohesion apply here just as they do to methods on structural types, only now you have a new way in which to express those concepts which leverages type inference to create runtime flexibility.
In situations where you need a Cowboy you want a type that reflects that, and nothing more than that. However often we confuse the concept Cowboy (a struct possessing properties) with those of Rider, GunFighter and a whole plethora of other aspects of Cowboyhood defined by the actions they perform. Whilst all of these things are nouns the are really aspects describing a set of verbs which we consider unique (at least in context) to the objects which implement them.
If there is one addition I would like to see to the language spec, it's an analogue to interfaces which allows a type to be described in terms of a subset of the structural properties it possesses rather than messages it responds to. In the Cowboy example, the probability is that a lot of the code I want to write related to tattooing and body art is really about the concept of Skin. I can achieve something similar through interfaces using methods for getters and setters, but it conflates two essentially disjunct concepts. Being able to know that a given type actually possesses Skin and that that Skin is of a type (concrete, interface or property class) that my code knows how to deal with in a certain place at runtime would add a whole new layer of richness to domain modelling.
> Okay, it doesn't make sense that someone would be both bandit and sheriff... at
> least by the rule of law... although it does happen in real life.
Which is very much the point. When you're duck-typing you're not abiding by the rule of law, but by the rule of thumb. And rules of thumb should by definition do as little to be prescriptive as is required to solve a particular problem.
So when designing interfaces we should apply Occam's razor to hone them to the smallest definition possible and when the compiler kicks up a duplicate method error we should look for new abstractions which better capture the underlying functional isomorphism of the type-space we're describing.
Over the last year I've had to relearn a static mindset to make good use of Go's structural typing mechanisms, an effort that at times has been very frustrating but ultimately worthwhile, I would highly commend the same effort being applied in the other direction by anyone coming from a static typing background if you really want to understand interfaces. Do some duck-typing in a language like Ruby or Smalltalk and see exactly how building complex message filters really affects the readability, maintainability, reusability and performance of dynamic code.
As the filter complexity increases what's really happening is that we're reimplementing static typing in a dynamic context, and hence without the rigour and safety which a compiler would introduce to the process. This results in very brittle code. Whereas embracing the strengths of dynamic typing leads to much more robust systems where that dynamism is desirable. After a few months of really trying to get your head into the duck-typing methodology you may also find a few of your entrenched beliefs about type relationships questioned in ways which will improve your Go code.
Ellie
Eleanor McHugh
Games With Brains
http://feyeleanor.tel/
----
raise ArgumentError unless @reality.responds_to? :reason
Just to clarify, I was using the editorial "we" and "you" rather than flaming anyone or being terse. I'm laid up with the flu and apparently my people skills are suffering as a consequence!
it's for that reason, i think that you won't find many (any?) interfaces in
the Go source tree of the form:
type T interface {
IncludedType
AdditionalFunctions()
}
instead, it's good practice to define the additional functions in their
own interface and create another to combine them.
type A interface {
AdditionalFunctions()
}
type T interface {
IncludedType
A
}
the io package, for example, uses this approach.
http://gaming.jhu.edu/~phf/pub/jmlc-2000.pdf
http://gaming.jhu.edu/~phf/pub/scp-composition-2004.pdf
The first paper is really enough for the current debate. Enjoy.
--
Peter H. Froehlich <http://www.cs.jhu.edu/~phf/>
Senior Lecturer | Director, Johns Hopkins Gaming Lab