3 ms·
A fascinating limitation of Go is its lack of support for functions with covariant return types. Say that you want a function which returns a Reader: type
by rectang 11y ago
A fascinating limitation of Go is its lack of support for functions with covariant return types.
Say that you want a function which returns a Reader:
type ReaderMaker func() io.Reader
A function which returns a concrete type which implements Reader cannot be substituted. This makes sense because interface types are always a two-word tuple, and a concrete type could be a one-word pointer, a big struct, etc.
var invalid ReaderMaker = func() *io.PipeReader { return &io.PipeReader{} }
More subtly, a function which returns a different but compatible interface type also won't work.
// This assignment is invalid because although anything that implements
// ReadCloser also implements Reader, a ReadCloser itable won't necessarily
// have Read() at the same offset as a Reader itable.
var invalid ReaderMaker = func() io.ReadCloser { return &io.PipeReader{} }
http://play.golang.org/p/5PPpVZhap_ http://play.golang.org/p/5PPpVZhap_
* edit: fixed swapped types in comment
- Manishearth 11y agoHuh, I thought Go doesn't use variance anywhere (probably because I hit this). Any situation where it does use variance?
- psynapse 11y agoI quite like Go and I'm writing a lot of it these days, but this is one part I really hate. Some of what you gain by its lauded "simplicity", you give back with type system wrestling.
- pcwalton 11y agoVariance isn't really applicable to Go at all, because Go doesn't have subtyping. It just has a lot of built-in type coercions in various places in the grammar, "interface to super-interface" being one of them. That explains why covariant-anything doesn't work in Go: there is no coercion between function types.
- DonaldFisk 11y agoI haven't learned GoLang, but what I want to say here is quite general. I regard that (class names ending in "er") as an antipattern, and have seen them again and again in Java programs. I call them "thingers". Except in simulation programs, objects should just sit there passively and have things done to them by methods. For example, rather than a parser class you should have a parse method, which belongs to a grammar class, and takes a list of tokens as its argument, and returns a parse tree. Here, it's simpler just to have a method/function/procedure called read() which can be an method belonging to whatever classes of object you want to read from. And if you want to make something, just call new rather than have a factory class. I'm not the only one who doesn't like thingers: see http://steve-yegge.blogspot.co.uk/2006/03/execution-in-kingdom-of-nouns.html http://steve-yegge.blogspot.co.uk/2006/03/execution-in-kingd... Now, before you argue that I don't grok object orientation, I'll tell you that I've added object orientation to Prolog, and to a Lisp dialect (twice). (I've also got several years of OO application programming, in Java and Lisp, behind me.) Perhaps you could call me an OO skeptic though - I can accept that.
- wlievens 11y agoReaders in Java are character-based streams. How would you implement a composite i/o stream model then, if Readers are bad-smell thingies? In Java. Adding buffering or counting or throttling to a stream is rather easy using this pattern; is there some other fundamentally sound way of doing that?
- DonaldFisk 11y agoYou've answered your own question. In Java, what's called a reader is nothing of the sort. It's really a stream (i.e. a thing, rather than a thinger), and I would have called it that, but programmers are now lumbered with it being called a Reader.
- wlievens 11y agoSo your answer is to search your vocabulary for a proper term that isn't a "nounified-verb"? I guess I do that anyway, since it feels cleaner, though just as often there isn't one, or it's already taken (like [Input|Output]Stream in my example), and I can't say I really feel that bad about nounifying a verb. In fact, when you nounify a verb (in other words: turn a function into an object) you can add so much to it. For instance, you could track how long it takes to execute, you can replace some of its constituent components (e.g. GOF Strategy Pattern), you could tune parameters that aren't part of its computational input (something like buffer size, or enabling caching, etc)... these are all things that would otherwise have to be communicated through a lengthy list of parameters, and turning them into object state frees up your actual method signature for the parameters that really matter.