5 ms·
Declaring Data as interface{} allows a struct to initialize Data, and therefore a node in this graphing library can store anything (including another graph). Th
by danieldk 12y ago
Declaring Data as interface{} allows a struct to initialize Data, and therefore a node in this graphing library can store anything (including another graph). This is probably one of the most important characteristics of Go
What? The void pointer has been around since at least the seventies. Ok, Go adds introspection by storing the type in a fat pointer. But...
and is why it is part of the reason Go cannot be considered object-oriented [5].
A don't see what this has to do with anything. Pre-generics Java, you'd do the same thing by making a field store Object (the superclass of every class). This also provides introspection like Go's interfaces. Then the Java folks concluded that generics are much nicer :).
tl;dr This form of genericity is provided by nearly any OO or imperative language. And in fact it points out a shortcoming in Go: it doesn't support parametric polymorphism.
- masklinn 12y ago> tl;dr This form of genericity is provided by nearly any OO or imperative language. And in fact it points out a shortcoming in Go: it doesn't support parametric polymorphism. OTOH the feature demonstrated here is discriminated unions, he's actually storing different types in the two nodes and is correct that most statically-typed languages don't have good ways to nicely handle that case (at least when the typeset is open, many languages handle the closed case nicely[0]). Not that Go handles it nicely of course, as you would in Java you have to use a cast/type assertion at every fetch, and that operation may fail at runtime. So you end up with val = heap.Pop(key).(*MyType) which may fault at runtime much like Java's val = (MyType) heap.pop(key) or val, ok = heap.Pop(key).(*MyType) if !ok { // handle failure } versus try { val = (MyType) heap.pop(key) } except (ClassCastException e) { // handle failure } [0] http://en.wikipedia.org/wiki/Tagged_union http://en.wikipedia.org/wiki/Tagged_union
- jamesaguilar 12y agoVoid* and java's object both support that idiom, at least to the degree to does. Actually, I'm having trouble thinking of a static language that doesn't support it at least as well as go.
- masklinn 12y ago> Void* and java's object both support that idiom I may be mistaken, but IIRC C-style casting from a (void *) provides no correctness guarantees, you may well end up with complete garbage unless you took care to explicitly check your union. And dynamic_cast requires RTTI. So the idiom is supported in the loosest possible sense, there's no language-provided safety. > at least as well as go. Yes, I think that's a point of contention and I edited my comment to expand on it: I really, really don't think Go supports this use case nicely.
- pcwalton 12y ago> I may be mistaken, but IIRC casting from a (void *) provides no correctness guarantees, you may well end up with complete garbage unless you took care to explicitly check your union. Right. In fact, in C (NB: not C++) you don't even have to cast, as the compiler allows it as an (extremely unsafe) automatic conversion :)
- waps 12y agoLanguages that support algebraic data types have a much better solution. You can explicitly declare which types can occur and it can be "A or B". It's called ADTs and it's the real solution to these problems. http://en.wikipedia.org/wiki/Algebraic_data_type http://en.wikipedia.org/wiki/Algebraic_data_type
- masklinn 12y ago> Languages that support algebraic data types have a much better solution. Which I mentioned in my original comment… > You can explicitly declare which types can occur and it can be "A or B". It's called ADTs and it's the real solution to these problems. Well no actually, an ADT is a single type with multiple data-divergent constructors (although GADT may fit the bill, maybe with datakinds, I've yet to understand those). In `data List a = Nil | Cons a (List a)`, the only type is List. Which is why I specifically noted that a closed set was necessary for tagged unions. There are languages which safely[0] handle actual type unions (genuine type coproducts), they are much rarer than types supporting simple tagged unions. IIRC, /u/munificent did a post on that for his magpie language although I don't remember if he ever implemented it. David Pierce's Whiley also seems to use type unions[1] [0] an important discriminant, C has unions but they're not memory — let alone type — safe. [1] http://whiley.org/2013/07/31/understanding-why-union-types-are-useful/ http://whiley.org/2013/07/31/understanding-why-union-types-a...
- danieldk 12y agoOTOH the feature demonstrated here is discriminated unions, He is storing interface{}, which is a fat pointer (a tuple of the pointer and type pointer). I don't see what is revolutionary and how most statically typed languages don't handle that case. Sure, in Java the type pointer is stored in the actual instance and not in a fat pointer, but it doesn't make a difference in data structure design. You can do the same abstraction in Java, C++, and C#. Which covers probably 90% of the statically-typed languages out there. Edit: note on the last Java example: an if statement with instanceof look prettier ;).
- masklinn 12y ago> Edit: note on the last Java example: an if statement with instanceof look prettier ;). Sure, and an if or switch on .(type) in go probably looks better as well.
- hornetblack 12y ago> switch on .(type) For anyone wondering switch val := heap.Pop(key).(type) { case (*MyType): // We're all good default: // Didn't get a (*MyType) }
- seanmcdirmid 12y agoLet's dissect [5] in the above: > Is Go an object-oriented language? Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. Classless prototype languages like JavaScript and Self also lack explicit type hierarchies, and they are still considered object-oriented. > The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There was a OO ML language called Moby [1] that had something similar. Just because the objects were structural didn't mean they weren't objects, though encapsulated communication (calling another object's private methods in the proper scope) is definitely more complicated. > There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes). You mean like extension methods in C#? > Also, the lack of type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java. They are basically claiming that Go is not C++ or Java, not that Go is not object-oriented, of which the design space is much broader. In my opinion, there is nothing interesting in Go, and it doesn't seem like a language I would want to use. [5] http://golang.org/doc/faq#Is_Go_an_object-oriented_language http://golang.org/doc/faq#Is_Go_an_object-oriented_language [1] http://cs.uchicago.edu/files/tr_authentic/TR-2003-10.pdf http://cs.uchicago.edu/files/tr_authentic/TR-2003-10.pdf
- masklinn 12y ago> There was a OO ML language called Moby [1] that had something similar. OCaml uses a structural object system (alongside the nominative type system it inherited from ML) [0] http://en.wikipedia.org/wiki/OCaml http://en.wikipedia.org/wiki/OCaml
- seanmcdirmid 12y agoThey are pretty contemporary, from the 2003 Moby paper: > The main differences stem from a fundamental design choice: OCAML’s design emphasizes the expressiveness of the object-type system, whereas MOBY’s design emphasizes the expressiveness of the class system. OCAML uses row polymorphism [R´em94] to support extensible object types, whereas MOBY uses structural subtyping. OCAML’s approach has two advantages: it is backward compatible with ML’s Hindley-Milner type inference, and one can get the effect of mytype by the use of recursive type variables. The cost of providing mytype is that OCAML’s classes are less flexible than MOBY’s. Specifically, if a method is public in an OCAML class, it cannot be hidden from either class clients or object clients of a deriving class, as required for the soundness of mytype. In addition, if a method is hidden from object clients in a class, it cannot be made public by a deriving class. The former restriction means that the class hierarchy determines an object-type hierarchy. These restrictions, along with the fact that OCAML does not have public fields, limit one’s ability to use the module system to implement friends via package scope in two ways. First, it is not possible to reveal inherited protected methods or fields to the friend functions, and any method that a class reveals to its friends must also be revealed to any client outside the module (assuming that the class is exported).
- cousin_it 12y agoIn the last few months I've been drifting toward the idea that parametric polymorphism is in fact enough for everything, and subtype polymorphism isn't needed at all. If you have subtype and parametric polymorphism in the same language, designing software becomes way too complicated. For example, look at this bullshit: public static <T extends Comparable<? super T>> void sort(List<T> list) There's no good reason why a simple concept, like sorting, must have a hugely complicated type that involves inheritance, F-bounded polymorphism and wildcards. It should be replaced with this: public static <T> void sort(List<T> list, Comparator<T> comparator) You can use that idea to replace pretty much all uses of subtype polymorphism with parametric polymorphism. The notion of interface inheritance, like "inheriting from" the interface Comparator<T>, is also unnecessary, because you can use a record type containing first-class functions instead. I wrote it up in more detail here: http://lambda-the-ultimate.org/node/4950 http://lambda-the-ultimate.org/node/4950
- seabee 12y agoThat was the goal of the C++ STL. Some may argue it goes too far, other opinions suggest it doesn't go too far enough; however I think most people can agree that you need typeclasses/traits/concepts et al. if you don't want to be doomed to passing different data structure manipulation functions to every call, as in your replacement example, or else resort to underspecifying the requirements of your type parameters and experiencing compiler messages from Hell.
- cousin_it 12y agoI'm hoping that some system like Scala's implicit arguments or Agda's instance arguments could ease the burden of passing stuff around. I don't know if that's enough for most realistic use cases, though. There's another complication. For operations that make sense on only some types, it makes sense to pass around the implementation of the operations, because it also acts as a type constraint. But for operations that make sense on all types, like Java's equals/hashCode/toString (and possibly also comparison), I suppose it's better to make them overridable for each type, as a special case in the language.
- 12y ago