13 ms·
> It’s better to compose than inherit I know that this is just a restatement of the "composition, not inheritance" mantra in Go, but it still makes about as mu
by rbehrends 9y ago
> It’s better to compose than inherit
I know that this is just a restatement of the "composition, not inheritance" mantra in Go, but it still makes about as much sense as "product types, not sum types". A more meaningful statement would be: "use inheritance to express sum types, use composition to express product types." There's no "better" relation between the two concepts, each has its own distinct purpose.
Yes, inheritance has traditionally sometimes been abused to implement a limited form of product type (for example, to work around Java's lack of proper value types), but that's a misunderstanding of what inheritance is used for.
> Go doesn’t have the concept of inheriting structs by design.
Go has automated delegation, and as we know, (automated) delegation IS inheritance [1, 2]. Some implementations of delegation are a bit more limited, some are a bit more expressive, but fundamentally they have the same purpose.
[1] http://dl.acm.org/citation.cfm?id=38820 http://dl.acm.org/citation.cfm?id=38820
[2] http://dl.acm.org/citation.cfm?id=900985 http://dl.acm.org/citation.cfm?id=900985
- pm215 9y ago'A more meaningful statement would be: "use inheritance to express sum types, use composition to express product types."' I'm not sure how well that works as a meaningful statement, because it assumes that the reader has a solid grasp of what a sum type and a product type are. If not (and I make the perhaps dubious assumption based on my own experience and knowledge that most programmers, especially most people using mainstream languages like Go or C++ or Python will not) then it doesn't really convey any information, unfortunately. I think a lot more people will know roughly what inheritance and composition are.
- nine_k 9y agoIf you find yourself not knowing something apparently important, maybe it's worth spending half an hour with Wikipedia and the like to gain the understanding? Among other things, the idea of a sum type helps understand the nature of the "billion dollar mistake" which is the inclusion of null in C, and why it pops up in other languages, and what more civilized methods of handling it might be. It will help you as a mainstream programmer, too.
- pm215 9y agoI did look at wikipedia, yes, which gave me a page of type theory related stuff without any apparent grounding in practicalities, which gives the conclusion that it's apparently unimportant unless you particularly like mathematical theory. Yes, I could go and research functional programming languages and type theory; but my point is that if you're critiquing a blog post on a non-functional programming language then doing it in terms that only FP advocates will understand is missing the target audience.
- kod 9y agoWhat wikipedia page did you look at? This has plenty of practicality https://en.m.wikipedia.org/wiki/Sum_type https://en.m.wikipedia.org/wiki/Sum_type
- pm215 9y agoI was looking at https://en.wikipedia.org/wiki/Product_type https://en.wikipedia.org/wiki/Product_type FWIW.
- tel 9y agoType theory isn't FP as much as logic--it applies everywhere. That said, totally agree that you're unlikely to have encountered it outside of an FP context in 2017. Product and sum type are fundamental structures for construction of information. A product of two pieces of information is a piece of information equivalent to having both at once: it's sort of like "and". A sum of two pieces of information is a piece of information equivalent to having exactly one or the other and knowing which of the two you have: it's sort of like "xor".
- rbehrends 9y agoA sum type `T = A | B` means that a value of type `T` can be either of type `A` or of type `B`. Such a type is used to express polymorphism. A product type (from "Cartesian product", i.e tuples) `T = A * B` means that a value of type `T` has a component that is of type `A` and another component that is of type `B`. It is used to aggregate parts into a whole. > Yes, I could go and research functional programming languages and type theory It has absolutely nothing to do with functional programming and touches only upon the barest essentials of type theory (and calling it type theory is already stretching it, because it's just about defining a couple of common computer science concepts). Sum and product types are fundamental computer science vocabulary to such an extent that it's not really possible to have a useful discussion about programming language semantics without them.
- rbehrends 9y ago> I'm not sure how well that works as a meaningful statement, because it assumes that the reader has a solid grasp of what a sum type and a product type are. I think you're misparsing what I mean by "meaningful" here, which is simply a sentence or phrase having (more) meaning, not that it's easy or easier to understand. It's not about being more comprehensible, it's about being more "accurate" (I avoided the terms "accurate" and "correct", because they imply an absolute objectivity that you generally don't have when talking about software engineering concepts).
- coldtea 9y ago>I'm not sure how well that works as a meaningful statement, because it assumes that the reader has a solid grasp of what a sum type and a product type are. That's true about all statements: they assume some prior knowledge from the one hearing them. That doesn't make a statement less meaningful -- just more demanding. Whether a statement is meaningful or not is an orthogonal concept (to it requiring prior knowledge).
- yen223 9y agoThat's ... sad. Sum types and product types are simultaneously easier to understand and are more useful to know about than inheritance. Sum types are simply types whose values can be one of several choices - surely that's something a child can reasonably understand! Product types might be a little harder to grok - they're essentially structs - but surely no harder to understand than inheritance and the whole IS_A/HAS_A mess
- s_ngularity 9y agoInheritance is not a way to express sum types, it's a form of subtyping. A sum type is like a discriminated union, it can only be one thing at a time. Subtyping allows a value to have multiple (related) types simultaneously, which is much more expressive. I suppose you can use one level of single inheritance to emulate a sum type, but you could just as well emulate it with a discriminated union in Go, e.g. a struct with a type identifier and an interface{} holding the value.
- skybrian 9y agoI'm finding that wrapping an interface in a struct can be a good technique. However, the interface{} contains a type identifier, so adding another one seems like wasted space. Usually you can compute it using a type switch.
- s_ngularity 9y agoYeah, that's a perfectly valid solution as well. Depending on the application it may be faster to do the assertion on your type identifier rather than introspecting the type though. And having a list of type options more directly maps to a true sum type. I probably wouldn't actually do it in real Go code if I could avoid it though
- rbehrends 9y ago> Inheritance is not a way to express sum types, it's a form of subtyping. A distinction without a difference. This is probably most visible in languages like Scala and Kotlin, which implement algebraic data types by way of inheritance. That inheritance creates a subtyping relationship is irrelevant; there's a similar subtyping relationship between variants (or groups of variants) and the overarching type using a traditional sum type notation as in Haskell or ML. This is most clearly visible in OCaml's polymorphic variants [1, 2]. [1] http://caml.inria.fr/pub/docs/manual-ocaml-400/manual006.html#toc36 http://caml.inria.fr/pub/docs/manual-ocaml-400/manual006.htm... [2] https://stackoverflow.com/questions/16773384/why-does-ocaml-use-subtyping-for-polymorphic-variants https://stackoverflow.com/questions/16773384/why-does-ocaml-...
- didibus 9y ago"but that's a misunderstanding of what inheritance is used for" I'm not sure what you imply. Most OOP languages where people tell you not to use inheritance, but composition instead, so java, c++, c#, etc. In those languages inheritance can be used to create forms of product types, but also to share and override behaviour hierarchically. They can also create sum types, and all possibility of hybrids, like weird mix of sum and product types, partially closed, etc. By allowing inheritance to do all this, I say it doesn't matter what the designer of these languages intended inheritance to be used for, the truth is that it allows for much more, and so best practices have been put in place to help programmers not use the construct in troublesome ways. One of those is to use composition instead. Can you simulate closed sum types with inheritance, yes, but they are not the same thing clearly, since closed sum types can not emulate all usage of inheritance (the kind of java). Maybe you're right, a closed sum type pattern could be created and evangelized, having an abtract class with no fields and a set of methods, then having a one level inheritance hierarchy where each subclass has its own disjoint set of fields, and overrides all methods to work on its fields. But I already feel like in practice this sounds like a nightmare. Too much good intentions are needed to maintain this, its too easy to create a degenerate case of it.
- rbehrends 9y ago> I'm not sure what you imply. Most OOP languages where people tell you not to use inheritance, but composition instead, so java, c++, c#, etc. In those languages inheritance can be used to create forms of product types, but also to share and override behaviour hierarchically. They can also create sum types, and all possibility of hybrids, like weird mix of sum and product types, partially closed, etc. My point wasn't to give an exhaustive list of use cases for inheritance (which would require a small essay); I was pointing out that "composition over inheritance" is a nonsensical statement, just as (say) "loops over modules" would be, as it's a qualitative comparison of orthogonal concepts.
- didibus 9y agoOkay, but its not, not from the perspective the best practice comes from. The most common use cases for object inheritance can be delivered with object composition instead. This is much more like loop vs recursion.
- camus2 9y ago> Go has automated delegation, and as we know, (automated) delegation IS inheritance [1, 2]. Some implementations of delegation are a bit more limited, some are a bit more expressive, but fundamentally they have the same purpose. No it is not inheritance, and Go doesn't have automated delegation, it has type embedding. Given struct A, if a function requires A, you can't pass any type B that embeds A, you must pass A. type A struct { Foo int } type B struct { A } func acceptA(a A}{} // you can't pass B here
- rbehrends 9y ago> No it is not inheritance, and Go doesn't have automated delegation, it has type embedding. Different names for the same thing. > func acceptA(a A}{} // you can't pass B here This says that the function isn't polymorphic in its argument, not that the types aren't polymorphic. Function resolution that is not polymorphic is not limited to Go, but occurs in inheritance-based languages, too. Example in OCaml: class a = object method foo = 0 end class b = object inherit a method bar = 1 end let f (x: a) = () let () = f (new b) You will get an error that the type of `new b` (= `b`) is not compatible with `a`, because they're not identical, even though `b` is a subclass of `a`. If you replace the declaration of `f` with: let f (x: #a) = () it'll work, because `#a` denotes a polymorphic type, matching `a` or any subclass of `a` (same as though you'd specify an interface in Go [1]). You can also cast the type explicitly to `a` to work around the error. [1] Like Go, OCaml uses structural subtyping.
- camus2 9y ago> Different names for the same thing. No, different names for completely different concepts. > This says that the function isn't polymorphic in its argument, not that the types aren't polymorphic. Function resolution that is not polymorphic is not limited to Go, but occurs in inheritance-based languages, too. Example in OCaml: Struct types in Go are not polymorphic in anyway period, the only way to achieve polymorphism in Go is through interfaces which are not concrete types, unlike classes. > [1] Like Go, OCaml uses structural subtyping. No it doesn't. There is no subtyping in Go. There is only type conversion and type assertion. Whatever you wrote with OCaml is completely irrelevant to the discussion as the type systems are fundamentally different. but let's pretend it is. It's interesting that you didn't bother try writing the equivalent of `let f (x: #a) = ()` in Go, because you CANNOT. An interface IS NOT a substitute for OCaml inheritance, as the later is more precise and specialized. So no, Go doesn't support inheritance at its core. That's a false assertion. Go interfaces do not give a damn about what the actual implementation is, unlike OCaml sub classes.
- KirinDave 9y agoWhat affordances does Go even have for real composition though?