6 ms·
Avoiding Reflection (And Such) in Go
- axaxs 11y agoMaybe I am misreading, but the Little Types section I think recommends an ugly habit, especially for libraries. When calling this "func Set(ID, Width, Height, Color)", assuming I acquire integers through user input, this means my code would then look like - extLibrary.Set(extLibrary.ID(id), extLibrary.Width(width), extLibrary.Height(height), extLibrary.Color(color)), which frankly is a nightmare. I'd expect the function to take 4 integers, then convert internally if needed. Further, it makes Godoc more confusing, since now I have to search around to figure out what each of these custom types is.
- jerf 11y agoAs a general rule, convert incoming input (user or otherwise) to the correct type as quickly as possible, and convert it to the outgoing type as late as possible. When programming in this style, for you to reach the point in the code where the Set call is being made and for you to still have 4 "int"s almost certainly means you should have converted them earlier. Generally you've got a validation phase somewhere, which is where this should be converted, which means that your "Width" isn't just "what happended to be in the input" but is a "validated Width" as well. Since it floats on the validation code it's often not even 1 extra line of code, just another token that I mentally model as sort of "sealing" the fact that this is a good value. This rule goes way beyond "tiny types"; for instance you will drive yourself insane in the web world if you don't do this, with double-decodings and double-encodings quite challenging to prevent with any other policy. [1] "Further, it makes Godoc more confusing, since now I have to search around to figure out what each of these custom types is." Absolutely yes. In any language where this is possible and easy, I do wish the auto-doc systems either automatically, or with some metadata (preferably a label on the type somehow), would make it clear when something is just a one-layer wrapper around a simple type in all the places where it appears in the docs, without having to click through. I consider it worth the cost, but this is definitely an entry on the cost column that is accidental, not essential. This is especially true when the base type is numeric, which means that Go will let you pass in a literal number in the source code, but that is not always your first expectation. [1]: In a statically-typed language you can also play games with having the type label what "layer" of decoding the value is currently in, which works but gets more tedious. This is theoretically possible in a dynamic language, but goes against the grain, since generally people program with just 'strings' in those languages.
- carbocation 11y agoIn this example, Set() is in the public API, so it is exposed to the end user. I am interested both in your approach and in axaxs's concerns, and I'm wondering if there is resolution to be had.
- rpedela 11y agoUsing little types is fine, but I prefer to not use them nor do I recommend them. I have dealt with several large C/C++ codebases which used them extensively. It often took too much time to find the declaration to see the real type. Then I would forget and repeat the process again and again and again. Overall they wasted more time than they saved. Some of the fancy IDEs help with this problem, but not everyone likes using them so you can't count on that as a solution.
- wtetzner 11y agoI suppose it depends on what you're using the tiny types for. For example, if you have a unique ID type, you might have a function called nextId(), and it gives you a new UniqueId. But it really doesn't matter what the "real" type is. It could be an int, or maybe a UUID, or maybe generated string.
- rpedela 11y agoIt could matter, depends what you are doing. But yes your larger point that they can be useful sometimes is true. I would generally recommend that tiny types should be used sparingly when there is a good reason rather than most or all of the time like the OP seemed to be suggesting.
- echlebek 11y agoHi, I enjoyed your article. You have some good points about tiny types, but if you read through the code in the Go standard library, you won't find this style applied in very many cases. Typically, only if one wishes to attach a method to a built-in. As another poster rightly pointed out, it complicates the documentation quite a bit, and I think a lot of Go programmers would consider it pointless abstraction. // NewBox creates a new Box. It returns an error if length // or width are less than or equal to zero. func NewBox(length, width float64) (Box, error) { ... } Consider the simplicitly of NewBox. We may omit a Length and Width type, and any validation of these by stating the expectations of the inputs in the documentation. So, the API is dramatically simpler. The expectation is that the programmer will not pass invalid arguments to NewBox, by way of common sense or reading the documentation. Programmers who pass in positive numeric literals may even opt to ignore the error returned, since the documentation states the the only reason an error would be returned is if the input parameters were <=0. Go puts a big emphasis on documentation, and this often compensates for lack of power in its type system.
- ngrilly 11y agoThis is an excellent, well researched and well written article, that shows a lot of great things can be programmed in Go, despite the apparent simplicity of the language, while staying honest about the limits of the language for some use cases.
- eikenberry 11y ago"despite the apparent simplicity of the language" Simplicity is not a weakness or failing of a programming language, it is something that every language designer should strive for. To imply otherwise is to say (non-essential) complexity is a good thing.
- wpietri 11y agoI think the contrast drawn isn't with a simple language, but with a too-simple one. As Einstein was paraphrased, "Everything should be made as simple as possible, but no simpler."
- pspeter3 11y agoAgreed. I feel like I'm never quite sure where Go falls on this line.
- eikenberry 11y agoWhere Go falls on this line depends on your conception of a programming language. If you look at a language as static definition that doesn't change with time, it would be easy to see Go as too simple. But looking at a language as an organic thing that grows over time, I think Go's simplicity and resistance to change generally gets it about right. To many modern languages suffer a complexity problem from years of accrued features.
- ngrilly 11y agoI fully agree. Bad choice of words on my part.
- deleted 11y ago
- bndw 11y agoExcellent explanation of composition over inheritance in Go. As someone new to Go, this was invaluable.
- pcwalton 11y ago> Because composition doesn't intermingle objects the way inheritance does, it allows much more interesting "chains" of objects to be created that extend each other, without the complexity exploding. Go's (really Plan 9 C's) composition support does "intermingle" methods, which has pretty much the same effect as intermingling fields. To see this, note that Go needs rules about who wins if methods for an outer object and an inner object have the same name (and they have to consider diamonds, which you can have with composition!) > So, "Go doesn't have generics" is only half true. I know this is going to be me annoying about terminology, but I really disagree. :) Go has polymorphism. It doesn't have generics, at all. "Generics" has a specific meaning: the ability to take types as parameters. > It can be profitably argued that the primary virtue of "generics" is generic algorithms rather than generic types. I don't agree. Generic types are necessary, because you often can't express generic algorithms naturally without generic types. Just as an example of where I needed generics in my work most recently, I wrote a polygon clipper over the past couple of weeks that has special fast paths when it clips rectangles and falls back to Sutherland-Hodgman clipping for complex polygons. This was natural to express by making the clipping result generic over the type of polygon (rect or edge list). I could maybe have done it in Go by using the "container-as-interface" trick that the sort package uses, but that would introduce a lot of boilerplate. There's also the issue that all these interfaces make it awfully hard for the optimizer to produce as good code as it would if you actually had generics. Unless you inline everything away (in which case SROA and constant propagation can mostly get you there), you'd need a nontrivial interprocedural analysis. Performance issues around generic programming tend to have outsized effects, because functions that tend to be generic are precisely those functions that tend to be called a lot (because they're generic!) and shoot to the top of the profile. > As a side note, if Go ever does grow full generics, I expect it to do so by extending the half it has, rather than adding a new concept of generics on the side. It's easy to imagine a generic BalancedBinaryTree that requires its nodes to conform to a LessThan interface. This I agree on entirely. Note that this is pretty much exactly what Swift did :) > If you have a constrained set of messages, for instance, you can use the type system to more carefully declare them as coming from a particular set of types, not just interface{}. But you lose half of the point of sum types, in that the compiler can check exhaustiveness for you. It's worse, because since you're doing a Go type switch, you can match against cases that aren't even in the sum type and the compiler won't complain! It's also an awful lot of boilerplate to create a half-approximation-of-a-sum-type, meaning that the ergonomics favor people not using it. interface{} is just plain easier to use compared to the sum type pattern, which is why it gets so much use. > Go still manages to cover so much with its meager feature set. There's a lesson here for language nerds. I don't fully know what it is yet, but I'm working on it. I think the lesson is "people will figure out design patterns to approximate things that your language doesn't support". This is no surprise: people write OO systems in the C preprocessor. But I think it would be a mistake to conclude "omit useful features from the language if there's any way people can possibly approximate them". This was the approach that Java famously took, and people rightfully soured on the extreme emphasis on "design patterns". And what you've described in this post is nothing more than a set of design patterns. Design patterns have their place and are very useful, but they're also the sincerest form of feature request.
- jbeja 11y agoYou Go people really likes the pain.
- jonesb6 11y agoI'm going to get stabbed in a dark alleyway for this, but I would argue that most Go projects don't benefit from being perfectly go-ish. It's a small, simple, language that is terrifically pragmatic. Write code that's clear, self-documenting, and works. Then go back and fix up performance, convention, and style, as needed. I wonder how many thousands of hours has been spent talking back and forth over an ultimately trivial part of Go. ...... Solid articlie though. As someone who writes a lot of Go, is sick of hearing about Go, I enjoyed it.