10 ms·
Maybe adding generics to Go is about syntax after all
- cyphar 8y agoOn the topic of "syntax is important" (though not related to generics -- other than by aside to the Result type) I'm still not a huge fan of the handle syntax -- I reckon something more similar to Rust's .map_err() would be handled better because lots of people now use pkg/errors[1] which has the boilerplate of if err != nil { return errors.Wrap(err, "some message that is unique to this line") } and the handle construct doesn't really handle (excuse the pun) this properly (you have to re-define handle each time or have a local variable or something similar). Which means that the new construct won't actually be massively helpful unless the only thing you want to do is errors.WithStack or just bubble the error back up. But I do get why they couldn't get .map_err() -- because that's something you can only really do if you have a Result type. [1]: https://github.com/pkg/errors https://github.com/pkg/errors
- ape4 8y agoC/C++ __LINE__ is unique to a line.
- cyphar 8y agoI'm not sure you understand what I mean -- the "unique to a line" message is actually a descriptive message not a line number. errors.WithStack gives you the file, line, and function name for free and works with handle -- my complaint is that giving descriptive and stacked error messages will be very hard with handle (but is not fundamentally hard with Rust's map_err even though their error packages aren't as well-developed as Go's from my experience).
- Someone 8y agoNo, it isn’t, not even within a single file. https://gcc.gnu.org/onlinedocs/cpp/Line-Control.html https://gcc.gnu.org/onlinedocs/cpp/Line-Control.html: ”If you write a program which generates source code, such as the bison parser generator, you may want to adjust the preprocessor’s notion of the current file name and line number by hand. […] #line linenum linenum is a non-negative decimal integer constant. It specifies the line number which should be reported for the following line of input. Subsequent lines are counted from linenum. #line linenum filename linenum is the same as for the first form, and has the same effect. In addition, filename is a string constant. The following line and all subsequent lines are reported to come from the file it specifies, until something else happens to change that. filename is interpreted according to the normal rules for a string constant: backslash escapes are interpreted. This is different from ‘#include’.”
- ainar-g 8y agoYeah. I always try to include some context with my errors, so the only actual uses for check/handle I have is handle err { log.Fatal(err) } in quick "scripts". And even there I should probably provide more context. All clean-up work is already done in defer. Overall, check/handle feels like a poorly-thought slap-on to me, on par with ON-units from PL/I[1]. And PL/I is not something you want as an inspiration. [1] https://en.wikipedia.org/wiki/PL/I#ON-units_and_exception_handling https://en.wikipedia.org/wiki/PL/I#ON-units_and_exception_ha...
- cyphar 8y agoRight -- people already use defer as a way of mapping errors and doing cleanup in an already awful way: func something() (Err error) { defer func() { if Err != nil { someCleanup() Err = fmt.Errorf("wrapping: %v", Err) } }() return blah() } And unlike handle you can actually give an arbitrary mapping function, while as far as I can tell you'd need to do something like handle err { return mapError(err) } Rather than the more ergonomic handle mapError Or handle func(err Error) { return mapError(err) } But whatever -- all of this is pretty useless. The main benefit of check in my mind is actually that you would be able to easily bump your test coverage -- because Go test coverage is based on lines and so the repeated use of if err != nil { return err } artificially dilutes your test coverage (you don't need to test that every error is propagated -- that's just doesn't make any sense and might not be possible if you are returning directly from os.* functions that don't even have fault-injection).
- ljm 8y agoI enjoy the Go authors' attempts to propose novel or innovative solutions to existing problems, without going straight towards the status quo, but I do wonder if sometimes they're trying too hard. At risk of cargo culting or not knowing enough about the problem space, I can only think of what I've recently read from The Psychology of Computer Programming^0. The entire reason that generics and error handling are difficult is because of the decision to avoid a syntax table. This to me feels like an artificial constraint because the Go authors are optimising for compiler simplicity and not developer ergonomics. So the developer has to parse a whole lot more because the compiler guys don't want to take a hit. The reason why generics are such a pain in the ass, and errors are such a pain in the ass, is because the emotional attachment to how Go currently runs and what it stands for is still far more powerful than any attempt to innovate on the language's own status quo. So what Cheney says makes some sense in that in a lot of ways, it feels more intuitive than implementing language grammar that isn't executed but looks like real code. But at the same time, the whole debate is basically around how to change go without changing it. Either do it or don't. [0]https://leanpub.com/thepsychologyofcomputerprogramming https://leanpub.com/thepsychologyofcomputerprogramming
- pdeuchler 8y agoAs someone who just went to gophercon I'm still confused why they're tackling error handling first, and then generics. I would imagine tackling errors in Go 2.1 with the help of generics would make for a much much cleaner solution... if we even need one (i'm not fully convinced verbose error checking is as bad as people say it is)
- cyphar 8y ago(I agree that waiting until generics were ironed out would have been a better idea, because we already have pretty large communities around existing error handling libraries. Unfortunately this is a pattern of the Go language designers -- they have often ignored community consensus around an issue, like they did with vendor/ and other similar issues). My main issue with it (aside from making memes about RSI) is that it actually dilutes your test coverage artificially. 'go tool cover' instruments line-by-line (technically I think it's statement-by-statement but that's not relevant here) and so having a dummy line like if err != nil { return err } where you aren't doing anything useful with the error decreases your test coverage over a line which is not only obviously correct but might also be impossible to test (some APIs have an error return even though they can never fail in most usecases -- and as a user of the library it's better to be safe and check the error anyway). A perfect example of this is the Read() interface from math/rand -- it is impossible for this to return an error and yet you definitely should check the return value from Read() and it would be irresponsible not to.
- slavapestov 8y agoThe problem you describe is not specific to Go. You should always test all error paths in production code. Using mocks or error injection can help with triggering otherwise “impossible” cases.
- cyphar 8y agoThat is definitely one argument (and I do see where you're coming from -- especially if your error path has a defer that does cleanup or something complicated like that). However, I don't think spending a significant amount of time mocking out every struct method that has an error return is a worthwhile investment -- the majority of 'if err != nil { return err }' cases are not going to be interesting and the ones that are usually don't even need mocking to test because they are significant enough error cases that they are easy to trigger using the real version of whatever struct. And of course you should test error paths to make sure that your code does validate things.
- fithisux 8y agoWhy not reuse Common Lisp macros?
- gnulinux 8y agoWhy not use lisp syntax, at all? Why do we have to reinvent syntax every single generation? I'm not an old school engineer (I'm in my early 20s and in my first job, freshly outta college) but I can't see why we don't use lisp, prolog or ML syntax for everything. Seems vastly better than C-like synaxes in all ways I can think of, easier to implement, easier to extend, easier to read (imho) etc... When I program even in a low caliber lisp like elisp (which I do routinely since I use emacs) it feels like syntax gets in my way less frequently compared to python, C, Javascript etc... I just wanna think about my program, and not the syntax. If lisp is not expressive enough, why not just ML? Why do we have to reinvent it so many times when syntax is not an interesting or relevant problem (e.g. Rust syntax)?
- jclulow 8y agoBecause not everyone agrees (thankfully!) that syntax is uninteresting or irrelevant, or that Lisp is some amazing global aesthetic or ergonomic maximum.
- AnaniasAnanas 8y agoWhy thankfully?
- AnimalMuppet 8y agoBecause syntax actually does matter, and Lisp is only a local maximum. Thinking otherwise causes pain, as people try to use Lisp in situations where it is not the best answer, and is therefore harder to use than other languages. So, "thankfully", because acknowledging reality here means not having to live with the pain of using a language that is sub-optimal for the problem you're working on.
- 8y ago
- rplnt 8y agoSome discussion on why it might be not: https://www.reddit.com/r/golang/comments/9cjw98/maybe_adding_generics_to_go_is_about_syntax_after/e5b8akq/ https://www.reddit.com/r/golang/comments/9cjw98/maybe_adding...
- foolfoolz 8y agoi haven’t written a whole lot of go, but from what i’ve done and what i see coming in go 2 i don’t really want to have to write a lot more. go felt like it was modern-vintage to start with memory management but also values/pointers. with a heavy emphasis on multi return error handling instead of generics (Either), nil instead of Option, and mutability by default and the future seems to be more weirdness. magic methods like handle. list comprehensions still uncertain. contracts sound like a backwards compatible break and duplicate of existing functionality. i used go to use libraries and low memory foot print for my small projects. but i bet there’s simpler routes out there
- jonathankoren 8y agoIf Go gets generics, maybe they’ll actually add an ‘implements’ keyword for interfaces and no more of this: var _ SomeIface = SomeConcrete{} var _ AnotherIface = SomeConcrete{} Just to see if you actually implemented all the signatures on the interface correctly.
- cyphar 8y agoActually one of the main benefits of Go over Java is the lack of a requirement for "implements" because otherwise it means that you cannot use a type in a context where the original developer did not intend it to be used (though I imagine you're implying that it would not be required). However quite a few Go developers now use interfaces with an unexported method specifically to stop users from misusing their types in a context they didn't intend. I guess it's come full circle.
- deleted 8y ago[deleted]
- artursapek 8y ago> However quite a few Go developers now use interfaces with an unexported method specifically to stop users from misusing their types in a context they didn't intend. I guess it's come full circle. What a horrible hack. Why bother with that? Either export your interface, your don't.
- cyphar 8y agoWell -- what if you have multiple implementations of an interface and you want users to be able to choose between them (or rather, you want them to be able to write functions around your interfaces)? I don't really like it much either, but it's a completely valid usecase for unexported methods. In fact I'm pretty sure this was an example from the language developers for why you can have unexported interface methods in Go.
- burntsushi 8y ago
- gregwebs 8y agoThis might be nice to use in the specific case. However, its important to note that in the general case, you need a type variable because it is used in multiple positions. In the function below (taken from the proposal), one is stating that T is the same type in all usages. func Join(type T strseq)(a []T, sep T) (ret T) This below version is different, each variable could be a different type (that satisfies strseq). func Join(a []strseq, sep strseq) (ret strseq)
- mopsy 8y agoAs I understand, the type would be implicitly applied to every func/struc in scope (which I assume would be the package) It work quite well for package like a Map so any data structure/algorithm. I suspect it would be less ideal for filter/map/reduce.
- arendtio 8y agoI think the syntax is essential to the problem. A few days ago I wrote a quite similar post about the difference between Generics and Interfaces: https://gist.github.com/arendtio/77dd4df5f4b19dc69da350648434a88a https://gist.github.com/arendtio/77dd4df5f4b19dc69da35064843...
- wbl 8y agoWhy not adopt standard ML's polymorphism semantics? Is there some type theory issue I don't know about preventing that?
- danharaj 8y agoI don't know much about Go. Does it have subtyping? Mixing polymorphism and subtyping isn't obvious. Stephen Dolan's thesis on algebraic subtyping [0] is the first really satisfactory answer, all previous ones having significant drawbacks, but it's only 2 years old, untested in a production language. Considering how conservative, bordering on reactionary, the philosophy of Go seems, I think it's politically untenable. [0] PDF: https://www.cl.cam.ac.uk/~sd601/thesis.pdf https://www.cl.cam.ac.uk/~sd601/thesis.pdf
- atombender 8y agoGo does not have subtyping. Its type system is sufficiently rudimentary that steering it towards ML would just be just too extreme at this point.
- fusiongyro 8y agoI wish the world adopted ML's modules and functors. Especially higher-order functors, which are kind of an extension of the standard. But I think Go is more worried about compiler performance, so if there are significant complexities in the surface syntax of generics or in their downstream consequences, it will be an impediment there. I think the people behind Go consider most type theory to be an impediment to productivity and would rather accept something simple with shortcomings they understand than try to do it "right." I think this is a perfect example of Unix's "worse is better" philosophy. Whether we agree with it or not is another question.
- AnimalMuppet 8y agoMajor props to Dave for realizing that his previous position (that adding generics had nothing to do the syntax) was mistaken, and for admitting so publicly.
- artursapek 8y agoI was at Gophercon and saw Russ's video where he spent 30 seconds explaining what generics were to the audience and showed a half-baked example of the syntax they are considering. I've been using Go for almost 5 years now and have no desire to see generics introduced. It would ruin what is currently a very simple and easy-to-use language. Am I the only one who is hoping this whole thing fizzles out?
- cmrdporcupine 8y agoIt's the number one reason I tried out but wouldn't return and do anything serious in the language. And I'm not the only one.
- artursapek 8y agoThere are numerous successful "serious" projects written in Go!
- cmrdporcupine 8y agoOf course there are, but I wouldn't want to be the one to write them.
- artursapek 8y agoOK - well, from my 4+ yrs of experience creating and maintaining a big Go project, take it from me that it's really a very pleasant and productive language. I am branching out and learning Rust as an intellectual exercise, but can't help but think about how obtuse its syntax is compared to Go. I will continue to choose Go in the future just because it's so quick to work with (while also providing solid safety and refactoring support). The `interface{}` thing rarely comes up as a problem.
- piano 8y agoIt's not just the interface{} thing that's annoying. From my point of view, Go has way to many annoyances to be decent. No constness. No protection against copying. No compiler warnings. Crap error handling. Public/private denoted by uppercase/lowercase letter (WTF?). Implementation details leaking into the language - such as the "typed nil" nonsense. Struct and interface embedding is insane. Multiple returns from functions but no tuples. Needlessly complex `for` syntax, needlessly complex receiver syntax. Needless magic channel operators (they could've easily just been functions). The tooling is also annoying with the most glaring deficiency being the lack of package versioning, which I believe is going to change (or it has already? I don't remember). And then there are tools like `go vet` which basically just attempt to make up for the compiler deficiencies in sub-par ways... Go has a very nice runtime, but the language itself and the tooling feels extremely half-baked. I'm curious whether Go 2 will improve upon this.
- ilovecaching 8y agoIt's been three years since I started writing Go professionally and I think adding generics to Go is one of the worst ideas I've ever encountered. I love Haskell, and I much prefer hindley-milner type systems, type classes, and real sum types. I see the value of generics where appropriate. Golang, however, made the tradeoff to sacrifice anything near that level of abstraction, and it's success might largely be attributed to the approach-ability of the language as a result of that tradeoff. Retrofitting generics onto the language will be at it's best a death sentence for the principles and ergonomics of the language. One thing that has become clear to me as I have spent time with Erlang, Haskell, and Go is that it's much better to use the right tool for the job rather than trying to manipulate a tool to fit every use case, which is precisely what adding generics is all about.
- oppositelock 8y agoI've also been writing a lot of Go professionally for a long time, and I've used every part of the language extensively, and I disagree. There are many times I remember where I've had to implement some sort of application specific data structure that's not built into the language, and generics would have made it a lot nicer. I made it generic by using interfaces, but then you have the problem of throwing away type safety, so to preserve it, you need wrappers which cast. It's not a given that adding generics would ruin the ergonomics of the language. I hope that whatever form generics do take in the end, that they remain simple. If I see template meta-programming in Go of the sort that occurs in C++, I think I'll scream, but that meta programming was to work around limitations in C++'s typing system which Go doesn't share.
- ilovecaching 8y agoWhat do you mean by nicer? I would imagine that something that is application specific would not require generics. Interfaces do not throw away type safety if they are used correctly. They are also better suited for the consumer of the type rather than the definer. One of your problems might be that you are prematurely abstracting your interfaces for your callers.
- deleted 8y ago