[HN Gopher] What I'd like to see in Go 2.0
___________________________________________________________________
What I'd like to see in Go 2.0
Author : AtroxDev
Score : 182 points
Date : 2022-02-04 10:54 UTC (12 hours ago)
(HTM) web link (www.sethvargo.com)
(TXT) w3m dump (www.sethvargo.com)
| pipeline_peak wrote:
| I've heard Go program's execution performance is near Java.
|
| Is this true because I can't think of anything more useless than
| that.
| bstpierre wrote:
| Serious question: what are the odds that go 2 ends up like python
| 3 and it takes the world over a decade of pain to migrate? (I
| like both python and go, and I'm _still_ maintaining a sizable
| body of py2 code.)
|
| "Backwards compatibility forever" seems like unnecessary
| shackles, and the language should be able to grow -- I've seen
| some nice proposals for improvements. I just wonder what the
| strategy is going to be for migrating code from go1 to go2 and
| how painful that's going to be.
| asah wrote:
| IMHO the long python3 migration was well executed and we're now
| comfortably on the back side of it. Reminds me of perl4=>5 and
| other big lifts.
|
| Yes, commercial codebases understaffed for maintenance are
| kinda stuck, just like any legacy system. IMHO the solution
| must come from the business model down. Also, security,
| compliance & cost can help drive priority.
| morelisp wrote:
| This is an absolutely implausible take. Perl 4's entire
| lifetime was about 5 years; released in 1991 and Perl 5
| utterly dominant by 1996. Python 2 shows no signs of an
| actual EOL to this day!
| oblio wrote:
| It wasn't well executed. The standard migration tool (2to3)
| was kind of considered a failure and the better one (six)
| wasn't ready, or even around at the time.
|
| Python came this || close to dying during the migration, its
| users all having moved to other languages. Primarily data
| science saved it and then some time passed and libraries
| moved on, etc, and after a while the cost benefit calculation
| started swaying towards Python3, probably after 3.3 at least,
| so 4 years after Python3's launch.
| mountainriver wrote:
| I've never heard that take, it seemed completely botched to
| me. The first couple versions of py3 didn't even work well,
| and they were still pushing people to cut over
| avg_dev wrote:
| It would be nice if there was a tool that re-wrote your Go-1
| code in Go-2.
| szastamasta wrote:
| Nope, this is exactly what has killed Python3. There is only
| one way forward - old code still has to work or people will
| simply stay on 1.0 forever.
| masklinn wrote:
| I mean... yes, but also, not even remotely.
|
| Languages like Rust have solved this through the concept of
| _editions_ (or whatever you want to call it), essentially
| package or module-level "compilation mode" which allows
| evolving the language without breaking the old code.
|
| The issue of the Python transition is that it was way too
| massive and the repercussion were way too widespread (e.g.
| they _necessarily_ leaked into the APIs) for this to be
| possible, despite Python having an entire mechanism to
| handle this with `from __future__` (though admittedly that
| being a file-level attribute makes it less than ideal to
| move the language forwards).
| szastamasta wrote:
| If you can make compiler work with both - new and old
| code, then yes, I totally agree. But Python expected you
| to transpile your code to new version and recompile while
| also updating many of your dependencies. And that's not
| something you easily can do in large codebases.
|
| I'm not sure how editions in rust work, but if compiler
| can compile all editions and link them in single binary,
| then its something I'm totally for. But if someone
| expects people to go through millions of lines of old
| code and make sure it all works fine after migration to
| new version of lang, then I'm not sure your language can
| succeed.
| masklinn wrote:
| > If you can make compiler work with both - new and old
| code, then yes, I totally agree.
|
| Which you can, technically even Python has that mechanism
| (`from __future__ import <thing>` can change the language
| syntax).
|
| However the ability to use that for the 2/3 transition
| was basically nil as an enormous number of _apis_ were
| impacted, the syntax changes were really the easy bits,
| the semantics were much more difficult to deal with
| (talking from experience).
|
| > I'm not sure how editions in rust work, but if compiler
| can compile all editions and link them in single binary,
| then its something I'm totally for.
|
| That is exactly how it works.
| tapirl wrote:
| I remember that someone in Go core team (Ian?) said Go 1.18
| is Go 2. That means Go 2 is back compatible with Go 1.
| masklinn wrote:
| Go-1 and Go-2 code could even cohabit using something similar
| to Rust's editions system e.g. the `range` behaviour could be
| something like a package opt-in.
|
| And half the article could be fixed by adding new APIs and
| deprecating the old ones.
| mseepgood wrote:
| > Serious question: what are the odds that go 2
|
| The Go maintainers already said that they don't have any plans
| to do an actual version 2.0 anymore. Generics turned out to be
| possible without breaking backward-compatibility.
| lolive wrote:
| Not a Go developper here, but let's take the example of Java
| when it got (for example) generics or functional features.
| There was NO going back. These were so fundamental improvements
| that the past was absolutely outdated as soon as those new
| features were available. May be the same thing will happen for
| Go
| chris_overseas wrote:
| While I agree with what you're saying, that's not the GP's
| point. They're talking about backwards compatibility, i.e.
| old code running on new versions. Java has been
| extraordinarilly good at that (maybe too much so), with the
| possible exception of Java 9. Try running Java 1.0 code on
| JDK 17 and it'll probably run fine. Try running Python 2 code
| on Python 3 and you're generally going to be in for a very
| rocky ride.
| masklinn wrote:
| Not sure how relevant that is, the old code worked fine in
| post-generics Java.
| lolive wrote:
| Technically yes. But, to me, the question is how
| developpers are changed by a given paradigm shift. It
| happened with generics, it happens with functional
| programming. So an evolution in the language _can_ just
| make its past versions instantly outdated. [afaiu, python 3
| did not do that. But my point is to say that it _can_
| happen.]
| masklinn wrote:
| But that's a completely different matter.
|
| The issue of Python 2 / Python 3 is that the two were
| _not compatible_ , which made the transition extremely
| complicated. That has nothing to do with the new version
| making the old one essentially outdated because of the
| usefulness of its contents.
| lolive wrote:
| Ok. My bad.
| brnt wrote:
| Not so serious question: what is Go2 and Python4 would be the
| same thing?
| kitd wrote:
| ie. they both become Nim ;)
| norman784 wrote:
| Rust solved this with editions[0], I don't know if is this
| feasible with Go.
|
| [0] https://doc.rust-lang.org/edition-guide/editions/index.html
| t43562 wrote:
| Everyone has their own gripes. Modules are what cause me the most
| pain in Go - especially where they're in github and I need to
| fork them and now change all the code that references them. I
| don't know if the problems are even tractable because the way it
| all works is so incredibly complicated and any change would break
| a lot.
|
| I would like to remove all the "magic" that's built-in for
| specific SCMS/repository hosting services and have something that
| operates in a simple and predictable manner like C includes and
| include paths (although obviously I don't like preprocessing so
| not that).
|
| As for the language, I like the range reference idea but my own
| minor pet peeve is an issue with pre-assignment in if-statements
| etc which makes a neat feature almost useless: //
| This is nice because err only exists within the if so we don't
| have to // reuse a variable or invent new names both of
| which are untidy and have potential // to cause errors
| (esp when copy-pasting): if err := someoperation(); err !=
| nil; { // Handle the error } //
| This however won't work: func doThing() string {
| result := "OK" if result, err :=
| somethingelse(); err != nil { // result does not need to be
| created but err does so we cannot do this. return
| "ERROR" } return result }
|
| I don't have any good ideas about how to change the syntax
| unfortunately.
| usrbinbash wrote:
| Easily fixed using lexical scopes: func
| doThing() string { result := "OK"
| { var err error if result, err
| = somethingElse(); err != nil { return
| "ERROR" } } return
| result }
|
| `err` is introduced in the lexical scope, `result` isn't so it
| still refers to the string from the surrounding scope. `err`
| does not pollute the surrounding scope.
|
| You can also try the complete version here:
| https://go.dev/play/p/kDEB11YdvSs
| travisd wrote:
| In my experience this isn't idiomatic Go and would definitely
| turn heads in a code review. But in such a small function,
| it's fine to just not worry about the lifetime of your
| variables (and in bigger functions you can often decompose
| into smaller functions).
| jatone wrote:
| yeah it sucks, I think IDEs should help here. 1 click uplift
| var error to parent scope.
| assbuttbuttass wrote:
| The fix is pretty simple, just declare err ahead of time:
| func doThing() string { result := "OK"
| var err error if result, err = somethingelse(); err
| != nil { return "ERROR" }
| return result }
| rocqua wrote:
| Part of the reason OP liked the `if assignment` was to avoid
| polluting the higher-level scope with a variable that is only
| needed during the if statement.
|
| Your solution fixes the error, but at the cost of losing the
| upside OP saw.
| jaggederest wrote:
| Is there no equivalent to a lexical scope let definition?
|
| let { var err := error scoped
| code }
| usrbinbash wrote:
| Of course there is: { var
| err error scoped code }
| ptomato wrote:
| yes, you can wrap code in {} to scope it.
| Gwypaas wrote:
| The issue is now you have a potential nil value hanging
| around in the code with no real reason for its existence.
| Three refactors and some moving around later and you manage
| to hit a runtime panic in production.
| fastest963 wrote:
| You should be able to use "replace" to use your forked module
| instead of the original and you don't have to change anything.
| jatone wrote:
| unfortunately replace is not supported well with modules. and
| they are hell bent on not supporting it well.
| infogulch wrote:
| You mean this?
|
| https://thewebivore.com/using-replace-in-go-mod-to-point-
| to-...
| jatone wrote:
| yes. now try to go install your application with the
| replace directive. it will silently replace the code with
| the original upstream version.
| cube2222 wrote:
| I've been doing forks of modules and using replace to use
| them in my code base extensively. I had 0 problems, it
| works marvelously.
|
| If you're doing some kind of global find/replace on fork,
| then something's definitely not right.
| jatone wrote:
| yes that is one approach. it shouldn't be necessary.
| lanstin wrote:
| Yeah it works fine. It also allows local co-development
| of the module and its user without waiting for new
| versions to be noticed
| mieubrisse wrote:
| I'd add one more to this list: proper enum types.
|
| We use enums heavily to force devs who use our code into good
| choices, but the options are currently:
|
| 1) Use int-type enums with iota: no human-readable error values,
| no compile-time guard against illegal enum values, no exhaustive
| `switch`, and no autogenerated ValidEnumValues for validation at
| runtime (we instead need to create ValidEnumValues and remember
| to update it every time a new enum value is added).
|
| 2) Use string-type enums: human-readable error values, but same
| problems with no compile-time guards, exhaustive switch, or
| validation at runtime.
|
| 3) Use struct-based enums per https://threedots.tech/post/safer-
| enums-in-go/ : human-readable error values and an okay compile-
| time check (only the all-default-values struct or the values we
| define), but it still doesn't have exhaustive switch, is a
| complex pattern so most people don't know to use it, and suffers
| from the mutable `var` issues the post author detailed.
|
| To my naive eye, it seems like a built-in, compile time-checked
| enum type with a FromString() function would help the community
| tremendously.
| ibraheemdev wrote:
| The type sets proposal for Go has already been accepted as part
| of the generics proposal [0]: type
| SignedInteger interface { ~int | ~int8 | ~int16 |
| ~int32 | ~int64 }
|
| Interfaces that contain type sets are only allowed to be used
| in generic constraints. However, a future extension might
| permit the use of type sets in regular interface types:
|
| > We have proposed that constraints can embed some additional
| elements. With this proposal, any interface type that embeds
| anything other than an interface type can only be used as a
| constraint or as an embedded element in another constraint. A
| natural next step would be to permit using interface types that
| embed any type, or that embed these new elements, as an
| ordinary type, not just as a constraint.
|
| > We are not proposing that today. But the rules for type sets
| and methods set above describe how they would behave. Any type
| that is an element of the type set could be assigned to such an
| interface type. A value of such an interface type would permit
| calling any member of the corresponding method set.
|
| > This would permit a version of what other languages call sum
| types or union types. It would be a Go interface type to which
| only specific types could be assigned. Such an interface type
| could still take the value nil, of course, so it would not be
| quite the same as a typical sum type.
|
| > In any case, this is something to consider in a future
| proposal, not this one.
|
| This along with exhaustive type switches would bring Go
| something close to the sum types of Rust and Swift.
|
| [0]: https://github.com/golang/go/issues/45346
| cle wrote:
| Personally I've run into more problems with strict enum types
| in distributed systems in a team setting, than I have with Go's
| lack of them. In that setting, strict enums are usually over-
| strict and eventually you back yourself into a corner in terms
| of being able to roll out new enum values.
|
| When there's no clear winner in terms of tradeoffs, I prefer to
| leave it out of the language like Go has done.
| amw-zero wrote:
| What problems did an enum cause you, and how was the enum
| responsible for the problem?
| ddulaney wrote:
| This is a known issue with Rust, for example. I have an
| enum with variants A and B. Somebody writes an exhaustive
| switch (match statement) that handles A and B with no
| default case. I add a variant C. Their code breaks because
| they don't handle C. Adding an enum variant was a breaking
| change.
|
| In Rust, the answer is #[non_exhaustive], which forces
| consumers to always add a default case. It's not a huge
| deal, just a known issue with a well-understood solution.
| cle wrote:
| IME the problem is the _default_ behavior. Rust, Java, et
| al have that same default behavior of defaulting to
| closed enums, and you have to opt-in to open enums.
| Whether that 's adding a type attribute in Rust or
| implementing special code in Java to handle the case.
| This is a footgun for distributed systems. If you don't
| get it wrong, then clients writing their own client-side
| software will get it wrong.
|
| I'm not disparaging closed enums, they are very useful in
| certain contexts, but they make it really easy to do the
| wrong thing when reading data off the wire. Given Go is
| focused on this exact domain (distributed systems), I am
| glad the language doesn't have them.
| bcrosby95 wrote:
| I guess I don't quite get this. What I'm seeing here is
| that a non obvious breaking change is being turned into
| an obvious one.
|
| If something is handling A and B, but you add C, the code
| probably needs to make sure it's handling C correctly.
|
| I use Java in my dayjob and the behavior you've outlined
| is how I always code things, but it's manual and doesn't
| happen at compile time: I provide default that throws a
| runtime exception.
| mseepgood wrote:
| > strict enums are usually over-strict and eventually you
| back yourself into a corner in terms of being able to roll
| out new enum values.
|
| Yes, they are poison for the evolution of a public API.
| [deleted]
| amw-zero wrote:
| Swift solved this problems with [non-exhaustive
| enums](https://github.com/apple/swift-
| evolution/blob/main/proposals...).
| dietr1ch wrote:
| Is it solved? How do you decide which enums are final and
| which ones need to be non-exhaustive to allow evolving
| the code?
|
| I think that Go's decision stems from protocol buffers as
| they allow to push new values through old binaries, which
| is a must once you grow enough.
|
| https://developers.google.com/protocol-
| buffers/docs/proto3#e...
| ninkendo wrote:
| Well, one thing is that enums are non-frozen by default,
| so you have to actively tag it as `frozen` if you want to
| put yourself in a scenario where you're never allowed to
| add cases.
|
| When clients use `switch` on a non-frozen enum from
| outside its defining module, Swift emits a warning if
| they don't have an `@unknown default:` case... so
| consumers of your enum will have to have default logic
| for handling new cases in order to avoid this warning.
| (Not for frozen enums though, for frozen ones it's enough
| to just cover the known cases in calling code, since the
| expectation will be that you can't update them.)
|
| So basically, if you don't bother thinking much about the
| problem, you can just avoid adding `frozen` and you'll
| probably get reasonable behavior where you can add more
| cases later. Using `frozen` should only be the case if
| there is some sort of logical impossibility for there to
| be more cases. Something like how `Optional` has .some
| and .none, but it's pretty obvious that nobody's going to
| go add a new case to it (what would a new case even
| mean?) Same with Result, and probably a bunch of other
| types I can't think of at the moment.
|
| Also worth noting that Swift treats intra-library code
| very differently than code that links from another
| library... if you use your own enums in your own module
| and don't make them public, it treats them as if they're
| always frozen... which is nice because it's your internal
| code and you can always update your own usages without
| having to worry about compatibility.
| dietr1ch wrote:
| > Well, one thing is that enums are non-frozen by
| default, so you have to actively tag it as `frozen` if
| you want to put yourself in a scenario where you're never
| allowed to add cases.
|
| Yeah, non-frozen by default makes a lot of sense. The
| only gotcha left is that you can't retract from adding
| frozen, but that's ultimately behavior you want and
| something that must be able to bite you back.
|
| > if you use your own enums in your own module and don't
| make them public, it treats them as if they're always
| frozen... which is nice because it's your internal code
| and you can always update your own usages without having
| to worry about compatibility.
|
| That's neat
| morelisp wrote:
| > which is nice because it's your internal code and you
| can always update your own usages without having to worry
| about compatibility.
|
| Unless your services talk to each other or share some
| external data storage? Which is actually really common?
| mieubrisse wrote:
| That's a fair concern: if everything is strict, then there's
| no option to incrementally roll out a new value. Maybe a
| proper enum type could always have an `Unknown` value, which
| would allow for the leniency while still forcing the use to
| think about (and handle) it at compile time?
| masklinn wrote:
| Oooor the language can have proper type-safe enumerated
| types of some sort, and if you're in a domain where that's
| an issue you don't use them.
| cle wrote:
| Go is focused on the distributed systems domain though.
| It's fine, even desirable, to have languages focused on
| particular domains, that make design decisions based on
| the constraints of the domain. In this domain, closed
| enums are footguns with costly consequences if you get it
| wrong.
| farresito wrote:
| As someone that doesn't work in that domain, could you
| give a short example?
| Gwypaas wrote:
| Rust supports #[non_exhaustive] attributes, forcing users
| to cover the generic/wildcard case even though you have
| already covered all existing ones. Although, I rather do
| versioning and a breaking change if possible. Put it on the
| parsing/interop level rather than deep in the code during
| runtime because it is very likely that your code is not
| correct without handling the extra case either way.
|
| https://doc.rust-
| lang.org/reference/attributes/type_system.h...
| cle wrote:
| I would be okay with an open-by-default enum type. (I
| think...I'm not sure I've ever encountered a language with
| open-by-default enums.)
|
| I'm still not sure if it's worth it, it's idiomatic Go to
| "fall-through" if-branches for default cases, which is the
| same when checking quasi-enums. The symmetry is nice and
| makes it very easy to read. But I could be convinced.
| rplnt wrote:
| > no exhaustive `switch`
|
| This is what linters will do for you by default.
|
| > we instead need to create ValidEnumValues and remember to
| update it every time a new enum value is added
|
| Code generators are first class citizens in go, and writing an
| icky but reliable test won't be too hard either.
| donatj wrote:
| > Use int-type enums with iota: [...] no compile-time guard
| against illegal enum values
|
| Create a new int type and use that for your enums. While you
| still can create an illegal enum value, you basically have to
| be looking for trouble. It's not going to happen accidentally.
| It's even harder if it's an unpunished type in a different
| package.
|
| See:
|
| https://github.com/donatj/sqlread/blob/91b4f07370d12d697d18a...
| mieubrisse wrote:
| As amw-zero pointed out though, a user could accidentally
| create the int type and it would only fail if you have
| runtime checking (which requires you to build it, either via
| a custom enum constructor that returns `error` or an
| `IsValid` function, which then require you to maintain the
| ValidEnums list).
|
| Int types also don't give you any guards when deserializing.
| friseurtermin wrote:
| One caveat here is serialization. Writing your (or another
| package's) enum to a database will get you in trouble if you
| ever want to add another value in the middle. Sure, you can
| be careful and should document this, but who knows
| Someone wrote:
| Not only that, but the person sending you the serialized
| object might be looking for trouble. Sending you an enum
| value that is outside the legal range might help an
| attacker get into your system.
| amw-zero wrote:
| This compiles: type Test int const
| ( T1 Test = 0 T2 = 1 )
| func TestSomething(t Test) {} ...
| TestSomething(17)
|
| So this isn't a good suggestion, because you can easily pass
| any int value and will not get a compiler error. You may as
| well be using strings at that point.
| donatj wrote:
| Fwiw a literal 17 in a function call, let alone anywhere
| outside an equation or constant definition is a code smell
| that should never make it past review.
|
| I see your point however.
| amw-zero wrote:
| Depending on code review instead of a static type system
| does not scale. Look at all of the memory safety security
| vulnerabilities that are solved by "simply making sure to
| manage memory correctly."
|
| Also: variableDefinedInAFarAwayModule :=
| 17 ...
| TestSomething(variableDefinedInAFarAwayModule)
|
| It's not always as clear as a constant value being passed
| to an incorrect type.
| donatj wrote:
| I don't understand your example here, that's not going to
| compile.
|
| variableDefinedInAFarAwayModule is definitionally type
| int and will not be cast. It is also unpublished, so you
| couldn't be using it for a faraway module?
|
| Your 17 in the previous example has it's typed determined
| at compile time which is why it can be a problem.
|
| see: https://go.dev/play/p/jEdAhKDeLy6
| amw-zero wrote:
| Ah thank you. That's slightly better then.
| EdiX wrote:
| This will not compile because the type of the variable is
| int not Test.
| rplnt wrote:
| I mean, that's a pretty common usage of enums, isn't it?
| TestSomething(T1 & T2)
| morelisp wrote:
| And this is the big, probably irreconcilable, difference
| in culture between the sum-typers and the compiler-
| assisted-named-valuers....
| schrodinger wrote:
| Your point is valid, but the Go philosophy depends on you
| following conventions to have reliable code. This is true
| all over the place, e.g. you can easily ignore errors.
|
| Other languages take a stricter approach, and maybe that's
| better. Not defending (although I like Go), but it's really
| more a language philosophy than a singular defect.
|
| As the other commenter noted, this should fail code review
| and you should be using the provided constants, and it
| should be clear to you. And if you disagree (which again is
| totally valid), you should use a stricter language--there's
| plenty out there!
| mkdirp wrote:
| I generally tend to use enumer[0] to generate some
| boilerplate code that can help with addressing this, e.g.
| the below would compile, but would error at runtime. There
| are probably linters out there that could catch this. With
| Go, linters are generally pretty good at catching this kind
| of stuff. package main
| import "fmt" type Test int
| const ( T1 Test = 0 T2 = 1 )
| func main() { t, err := TestString("T1")
| if err != nil { panic(err) }
| TestSomething(t) } func
| TestSomething(t Test) { fmt.Println(t.String())
| }
|
| Having said that, it seems weird to have to mimic enums, as
| opposed to actually having it. Doesn't feel like it would
| add much complexity, if at all.
|
| [0] https://github.com/dmarkham/enumer
| rplnt wrote:
| > as opposed to actually having it
|
| Like C or C++ do? :)
| mkdirp wrote:
| Right. However, C and C++ are far more complex, with no
| memory safety. Everything has it's ups and downs.
| mseepgood wrote:
| I don't think you would need a 2.0 (backward-incompatible
| language change) for any of this.
| tialaramex wrote:
| Exhaustive switch seems likely to be backward incompatible if
| done well.
|
| What you _want_ here is something akin to Rust 's match
| behaviour on enumerated types. If your alternatives aren't
| exhaustive, it doesn't compile. Now, Rust is doing that
| because match is an _expression_. Your day matching
| expression needs a value if this is a Thursday, so not
| handling Thursday in your day matching expression is nonsense
| - even though often the value of a match isn 't used and
| might be the empty tuple it necessarily must *have a value.
|
| It seems to me that today a Go Switch statement operating on
| days of the week can omit Thursday and compile just fine.
| Exhaustive switch means that's a compile error. If your
| "exhaustive switch" is optional or just emits a warning, it
| won't catch many of the problems for which exhaustive switch
| is the appropriate antidote.
| mseepgood wrote:
| > Exhaustive switch seems
|
| This would only make sense on an enum type, which would be
| a completely new thing, so it can be introduced without
| breaking backward compatibility. Constants and switch on
| non-enum values would stay, because they are useful
| independent of enum types.
| Gwypaas wrote:
| Also makes sense on numbers and other patterns where you
| can logically enforce exhaustiveness.
| mieubrisse wrote:
| Yep, agreed. Maybe a new hypothetical `exhaustiveswitch`
| keyword could be added that would be backwards compatible,
| but it doesn't seem very Go-like to have such similar
| functionality as separate keywords.
| dpatterbee wrote:
| I believe new keywords are not backwards-compatible as
| they will invalidate any code using that as an
| identifier.
| smasher164 wrote:
| Sum types overlap a lot with what interfaces over. So
| enhancing the language with proper sum-types would benefit
| from enhancing interfaces and switch statements.
|
| However, zero values throw a wrench into this. The zero value
| of an interface is nil, so enhancing interfaces would require
| you to address what happens with an uninitialized variable.
| One of the current proposals suggests that nil continue as
| the zero value.
|
| They _could_ introduce a totally different type, like a
| sealed interface, which doesn 't require a zero value, but
| that distinguishes between different types of interfaces, and
| I'm not sure how that'll be received.
| masklinn wrote:
| > We use enums heavily to force devs who use our code into good
| choices
|
| Beware, tho, that with many languages today you're not really
| doing that even when they advertise enums e.g. in both C# and
| C++, enums are not type-safe (not even `enum class`). Iota is,
| at least, a fair acknowledgement of that.
|
| > with a FromString() function
|
| That seems like way a step too far, is there any such "default
| method" today? And I don't think Go has any sort of return-type
| overloading does it?
| amw-zero wrote:
| Really not sure what you're talking about. If you use enums
| in a real language like Swift, Kotlin, Rust, etc., you can
| only construct the values of the enum. There are no ways to
| get around it.
| masklinn wrote:
| > Really not sure what you're talking about.
|
| Have you considered reading?
| lolinder wrote:
| Have you considered elaborating? I am likewise unsure how
| a language could get enums wrong, even after reading your
| post.
| masklinn wrote:
| > I am likewise unsure how a language could get enums
| wrong, even after reading your post.
|
| By allowing any value of the underlying type, even if
| they were not values of the enum. The language most
| famous for this is obviously C: enum
| foo { A, B, C }; int main() {
| enum foo x = A; printf("%d\n", x);
| enum foo y = 42; printf("%d\n", y); }
|
| This will print `0` and `42`
| (https://godbolt.org/z/Yq8qq5bzW), because C considers
| enums to be integral types and will thus implicitly
| convert to or from them. And as you can see from the
| link, there is no warning under `-Wall`. Clang will warn
| with `-Wassign-enum` (which is included in
| `-Weverything`), I don't think there's any way to make
| gcc complain about this.
|
| Now you might argue that this is C so obviously it fucks
| this up, however that C does this leads to further
| issues:
|
| - For compatibility with C, C# enums have the same
| property. I don't know about you, but that surprised me a
| great deal since Java has type-safe enums (even if
| they're not sum types).
|
| - Even more so, C++ added `enum class` in C++11, and
| obviously has inherited C's enums, but enum class _still
| is not type safe_ , afaik the differences are that `enum
| class` is scoped (so in the snippet above you'd need
| `foo::A`) and it's not _implicitly_ convertible with the
| underlying type. But it 's still _explicitly_ convertible
| (via a cast or list initialisation), meaning you can 't
| assume a caller will remain within the enum.
| infogulch wrote:
| This is only a problem in languages that don't support
| checked type safe enums / discriminated unions, but this
| whole thread was started to request that feature. I don't
| get the point of your comments unless it's to state "be
| careful or they'll do it wrong"... which is obvious and
| already acknowledged several times in this thread.
| amw-zero wrote:
| Friend - this is a learning opportunity. Check the
| upvotes.
| masklinn wrote:
| > Check the upvotes.
|
| Check the upvotes on my comment.
| rplnt wrote:
| Every time I see discussion about go and enums there are
| people who are referencing these mythical C-like enums that
| had never existed. It's some sort of constructed memory. And
| I'm sure there are languages that do enums "properly", but
| it's always C/C++ that is referenced.
| 13415 wrote:
| Ada does them well. They have attributes that allow for
| converting to and from integers and strings [1], and case
| statements have to be exhaustive or use a default "others"
| clause.
|
| [1] https://en.wikibooks.org/wiki/Ada_Programming/Types/Enu
| merat...
| masklinn wrote:
| The point here has little to do with Go, which you can see
| by the quote it replies to having nothing to do with go:
|
| > We use enums heavily to force devs who use our code into
| good choices
|
| the note is that this is very much language-dependent and
| there are languages which get it very, very wrong.
|
| > And I'm sure there are languages that do enums
| "properly", but it's always C/C++ that is referenced.
|
| That makes no sense, the original comment obviously assumes
| a language which does it "properly".
| rplnt wrote:
| Sorry, my comment wasn't trying to address (let alone
| bash) the original comment in any way here, just a
| tangent based on your note about C# enums.
| lucian1900 wrote:
| Or even better, proper sum types. They're a superset of enums
| anyway.
|
| https://github.com/BurntSushi/go-sumtype is great, but a bit
| unwieldy. Language support would be much better.
| catlifeonmars wrote:
| Proper compile time sum types would be great. I find myself
| reimplementing sum types at runtime far too often, especially
| when it comes to parsing JSON. My go to is a function with a
| signature along the lines of the reflect packages dynamic
| select:
|
| func UnmarshalOneOf(data []byte, args []interface{}) (index
| int, err error)
|
| Which I use like this:
|
| variants := []interface{}{ &T1{}, &T2{}}
|
| i, err := UnmarshalOneOf(data, variants)
|
| // ...
|
| return variants[i]
| slx26 wrote:
| Just adding to the discussion:
|
| I find this comment from Griesemer [0] on one of the github
| issues for enums in Golang quite insightful:
|
| >> [...] all the proposals on enums I've seen so far, including
| this one, mix way too many things together in my mind. [...]
| Instead, I suggest that we try to address these (the enum)
| properties individually. If we had a mechanism in the language
| for immutable values (a big "if"), and a mechanism to concisely
| define new values (more on that below), than an "enum" is
| simply a mechanism to lump together a list of values of a given
| type such that the compiler can do compile-time validation.
|
| Like with generics, I like the team's approach of taking
| features seriously, not adding them just because other
| languages have them, but actually trying to figure out a way
| for them to work in Go, as cleanly as possible. I think
| computer science, as a field, benefits from this approach.
|
| And I also dislike many things from Go, and I want "enums"
| badly too, but that's for another comment.
|
| [0]
| https://github.com/golang/go/issues/28987#issuecomment-49679...
| conaclos wrote:
| About the optimization of the regex package: a talk is scheduled
| tomorrow [0] to FOSDEM 2022.
|
| [0] https://fosdem.org/2022/schedule/event/go_finite_automata/
| christophilus wrote:
| This was a surprisingly good list. Most of these kinds of
| articles just consist of someone bemoaning the fact that Go isn't
| Haskell or whatever language they like more. But this is a
| legitimate list of things that could and should (in my opinion)
| be changed without turning Go into not-Go.
| rowanseymour wrote:
| Hardly important but I hope that in parts of the std lib where
| they added support for contexts by adding a `FooContext` func for
| every `Foo` and latter just calls the former with
| `context.Background()`.. we can just have `Foo` that takes a
| context argument.
| mariusor wrote:
| To expand on the logging improvements, I would like to see a
| `context.WithLogger` context function, to allow cross package
| passing of a common logger instance.
| honkycat wrote:
| What I would like to see:
|
| - Enun Types
|
| - A special operator to cut down on `if err != nil { return err
| }` and just return at that point.
|
| - Named arguments, and optional params with defaults
|
| - Default values on structs
|
| - ...macros? We already have `go generate ./...`
|
| ( edit: Removed unformatted source code )
| Jeff_Brown wrote:
| Sum types would satisfy the first two on that list, as well as
| making an ergonomic optional type trivial to define.
| Eun wrote:
| I would like to add proper JSON5 support.
|
| The template problem is a real problem. I used it once and it was
| pain and moved away instantly. I would vote for go inside go as a
| template system. So you can effectively write go code.
|
| With the help of yaegi[1] a go templating engine can be build e.g
| here[2].
|
| [1]: https://github.com/traefik/yaegi
|
| [2]: https://github.com/Eun/yaegi-template
| jerf wrote:
| You don't need official Go support for JSON5. Same for
| templating. The templating library in the standard library
| isn't anything special, it's just in the standard library. If
| you don't like it, go get one of the dozens of others on offer.
|
| I think there's a number of language communities you can "grow
| up" in that teach you that things in the standard library are
| faster than anything else and have had more attention paid to
| them than anything else, like Python or Perl, because those
| languages are slow, and things going into the standard library
| have generally been converted to C. I think that because I look
| in my own brain and find that idea knocking about even though
| nobody ever said it to me directly. But that's not true in the
| compiled languages, of which Go is one. The vast majority of
| the Go standard library is written in Go. The vast majority of
| that vast majority isn't even written in _complicated_ Go, it
| 's just in Go that any Go programmer who has run through a good
| tutorial can read, it's not like a lot of standard libraries
| that are fast because they are written impenetrably. (Although
| the standard library may have subtle performance optimizations
| because it chooses _this_ way of writing it in Go rather than
| _that_ way, it 's still almost entirely straightforward,
| comprehensible code.)
|
| If you want JSON5 or a different template library, go get or
| write one. The Go encoding/json or text/template don't use any
| special access or have any magical compiler callouts you can't
| access in a library or anything else; if you grab JSON5 library
| (if such a thing exists), you've got as much support for it as
| JSON or text/template.
|
| It's even often _good_ that it 's not in the standard library.
| I've been using the YAML support a lot lately. The biggest Go
| YAML library is on major version 3, and both of the jumps from
| 1 to 2 and 2 to 3 were _huge_ , and would have been
| significantly inhibited if v1 was in the standard library and
| the backwards compatibility promise applied. v1 definitely
| resembles encoding/json, but that's missing a lot of things for
| YAML. If Go "supported YAML" by having v1 in the standard
| library, everybody would be complaining that it didn't support
| it _well_ , and by contrast, asking anyone to jump straight to
| the v3 interface would be an insanely tall ask to do on the
| first try without any experimentation in the mean time. And I'm
| not even 100% sure there won't be a v4.
| 015a wrote:
| I disagree with this stance.
|
| To me, the community take on "stdlib vs libraries" is a
| cyclical thing; we're coming out of a cycle led by
| JavaScript/NPM, where everything is a library due in no small
| part to how HORRIBLE JS/NodeJS's standard library is. Go back
| further, and you run into the Python/Java world which had far
| more comprehensive stdlibs, and today Go's (and to a lesser
| degree, Rust's) rising popularity is bringing back more
| feature-complete stdlibs.
|
| So, it changes. And its alright for different languages to
| have different stances on how comprehensive their stdlibs
| should be. Go is absolutely an example of a language that
| wants tons of stuff in its stdlib; but its also a language
| which despises change, and thus we got a very awesome stdlib
| at v1, and limited improvements to it over the years.
|
| I don't feel the "YAML changes a lot" argument is valid. It
| does; but if an app needs YAML, they can choose to use the
| stdlib, or a library, and they'll have to keep up regardless.
|
| Putting it in the stdlib has tons of advantages. First: it
| increases the scope of parties affected by any breaking
| change, which naturally forces more deliberate thought into
| the change's necessity and quality. Second: it reduces the
| number of "things" code consumers need to update; from the go
| version itself & the YAML library & consuming code, to just
| the go version & consuming code (this has network
| productivity effects in only having to source one "breaking
| changes" changelog for your hit list on what needs updated).
| Third: it reduces multivariate dependency-dependency issues
| (eg YAMLv2 requires Go1.14, but we're on Go 1.13, so first we
| have to upgrade to Go1.14, then we can upgrade to YAMLv2).
| Fourth: it reduces the number of attack surfaces which
| security professionals need to monitor (all eyes on the
| stdlib implementation, versus N community implementations,
| strength in numbers). Fifth, less about YAML, but: stdlib
| encourages what I call the "net/http.Request effect"; if I
| want to write a library that does stuff with http requests,
| its nearly impossible to do that in a framework-agnostic way
| in JavaScript, because express does things differently than
| hapi etc; but in Go, everything is net/http.Request. So even
| if the stdlib doesn't have everything one needs, plugging in
| libraries to solve it is easier because everyone is using the
| same interfaces/structures.
|
| Obviously not everything can be in stdlib, so it comes down
| to the question: what belongs there. And in my opinion, every
| language is too conservative (except maybe Python, which
| strikes a strong balance). Many language teams will say
| "we're not including X because X isn't an ISO standard, or
| because its still changing, or because we're not sure on the
| implementation". These are all arguments out of cowardice and
| fear of asserting an opinion. Language designers have
| developed a deep, deep fear of opinions; because they
| believe, maybe correctly, that one of their opinions will be
| bad, and it will hurt adoption. The issue is; having no
| opinion just hurts productivity, and people will also move
| from your language toward more productive ones (see: Go's
| rise in popularity, versus JS's dependency issues).
| hpx7 wrote:
| > Go's templating packages should support compile time type
| checking.
|
| Does anyone know of a decent type safe templating package out
| there (for any language)?
| geenat wrote:
| I'd like to see ergonomics improvements, particularly to function
| parameters.
|
| The current best practice of "functional options" and long
| function chains results in far too many function stubs ... its a
| minimum of 3 extra lines per parameter. Parameter structs require
| a whole extra struct.
|
| Suggestion: Just borrow named / optional parameters from Python.
| It would cut down length and complexity of Go code drastically.
| the_gipsy wrote:
| Proper sum types, if only for proper error handling. The if err
| != nil dance is extremely verbose and error prone with
| consecutive checks.
| Jeff_Brown wrote:
| That could also alleviate the no-exhaustive-enum-checking
| problem mentioned in another top-level comment.
| sdevonoes wrote:
| A better template library would be a killer feature. Similar to
| what PHP has been since forever; the ability to write HTML using
| all the Go language's features.
| AtroxDev wrote:
| From Rob Pike on reddit regarding this post[0]:
|
| The and and or functions in the template packages do short-
| circuit, so he's got one thing already. It was a relatively
| recent change, but it's there.
|
| Non-deterministic select is a critical detail of its design. If
| you depend on a deterministic order of completion of tasks,
| you're going to have problems. Now there are cases where
| determinism might be what you want, but they are peculiar. And
| given Go's general approach to doing things only one way, you get
| non-determinism.
|
| A shorthand syntax for trial communication existed. We took it
| out long ago. Again, you only need one way to do things, and
| again, it's a rare thing to need. Not worth special syntax.
|
| Some of the other things mentioned may be worth thinking about,
| and some of them have already (a logging interface for instance),
| and some we just got wrong (range). But overall this seems like a
| list of things driven by a particular way of working that is not
| universal and discounts the cost of creating consensus around the
| right solutions to some of these problems.
|
| Which is not to discount the author's concerns. This is a
| thoughtful post.
|
| 0:
| https://old.reddit.com/r/golang/comments/s58ico/what_id_like...
| dewey wrote:
| > Again, you only need one way to do things, and again, it's a
| rare thing to need. Not worth special syntax.
|
| This really is one of the parts I like the most about Go. It
| really makes so many things simpler. Discussing code, tutorials
| and writing it.
|
| Every time I'm trying to do something in JS I have to figure
| out why every guide has a different way of achieving the same
| thing and what are the implementation differences.
| nightowl_games wrote:
| It'd be nice if he had at least hinted towards what 'the way'
| is for this problem.
| cle wrote:
| It's in the article he's responding to.
| tapirl wrote:
| > but (deterministic-select cases) hey are peculiar.
|
| It looks for most select blocks in Go code, it doesn't matter
| whether or not they are non-deterministic or deterministic.
|
| But, if the default is deterministic, user code could simulate
| non-deterministic, without much performance loss. Not vice
| versa (the current design).
| uluyol wrote:
| Er, when it comes to concurrency, non-determinism is usually
| cheaper than determinism. As soon as you care about ordering,
| you almost always have to synchronize, and that has a cost.
|
| Austin Clements (of the Go runtime team) wrote a paper that
| explores this in detail [1]. That was before joining the Go
| team, but the concepts are universal.
|
| [1] https://people.csail.mit.edu/nickolai/papers/clements-
| sc.pdf
| lanstin wrote:
| Any time there is more than one channel being selected for it
| needs to cover them all equally.
| tapirl wrote:
| Equality is meaningful only if at least two case operations
| are always non-blocking. This is rare in practice.
|
| In fact, in practice, sometimes, I do hope one specified
| case has a higher priority than others if they are all non-
| blocking.
| lanstin wrote:
| Wouldn't it be the case id one worker was pulling work
| asynchronously delivered from two places? I only use one
| go routine / one channel myself, but the name select
| itself very strongly implies it is a yield type operation
| where any of a number of async actions can wake it for
| their callback to run. Albeit without a callback syntax,
| it is async and better be fair.
| lanstin wrote:
| As far as priority goes, most interesting cases will have
| priority based on the data in the read, except for this
| specific case of a done chan el and a data channel. I
| used that pattern at first but have been moving away from
| it. To be sure i am mostly writing long lived processes
| with fixed pools of worker go routines and either never
| exit or exit based on WaitGroups determining the work is
| all done.
| tapirl wrote:
| Yes, it (the lack of deterministic-select) is only
| annoying for several special cases. For most cases, it
| doesn't matter whether or not the default behavior is
| deterministic.
| rplnt wrote:
| > user code could simulate non-deterministic
|
| I'm curious how?
|
| > Not vice versa
|
| There are pretty common patterns for this. At least for real
| word cases where you might have one special channel that you
| always want to check. Ugly, but in relation to the previous
| question, I don't see how one is doable and one isn't?
| tapirl wrote:
| > > user code could simulate non-deterministic
|
| > I'm curious how? if rand.Intn(2) == 0 {
| select { case:
| <-chan2_whichItreatTheSameAsChan3 // a higher priority
| case: 0xFF ->chan3_whichItreatTheSameAsChan2 }
| } else { select { case: 0xFF
| ->chan3_whichItreatTheSameAsChan2 // a higher priority
| case: <-chan2_whichItreatTheSameAsChan3 }
| }
|
| Yes, it increases verbosity to the other way, but no
| performance loss.
| morelisp wrote:
| How in the world is generating a random number and
| branching and doubling the number of instructions "no
| performance loss"?
| usrbinbash wrote:
| > Not vice versa (the current design).
| select { case: <-chan1_whichIWantToCheckFirst
| default: } select { case:
| <-chan2_whichItreatTheSameAsChan3 case: 0xFF
| ->chan3_whichItreatTheSameAsChan2 }
| tapirl wrote:
| Yes, as I have mentioned, there is performance loss,
| comparing to select { case:
| <-chan2_whichItreatTheSameAsChan3 // a higher priority
| case: 0xFF ->chan3_whichItreatTheSameAsChan2 }
| morelisp wrote:
| 1) Is there really a performance loss compared to _if
| select was deterministic_?
|
| 2) What in the world do you need such code for?
| usrbinbash wrote:
| The real usecases where I need deterministic select, are
| so few that a small performance loss doesn't matter to
| me.
| qaq wrote:
| I have much smaller ask struct type elision in function calls
| masklinn wrote:
| I have to say I've no idea what you mean. In function
| _definitions_ I'd interpret it as type inference (and would
| disagree) but you specifically talk about function _calls_ ,
| and consider it a small change. Can you describe what you're
| thinking of?
| coder543 wrote:
| I think I understand what they're asking for. Consider a
| function that takes in a struct as one of the arguments.[0]
|
| Currently, you would have to invoke it like so:
| svc.GetObjectWithContext(ctx, &s3.GetObjectInput{Bucket:
| bucket, Key: key})
|
| But... why do you have to type "s3.GetObjectInput"? The
| function is taking in a concrete type (not an interface) for
| that argument, and there is only one possible type that you
| can pass in... so I agree with the person above that it
| should be possible to elide the type like so:
| svc.GetObjectWithContext(ctx, &{Bucket: bucket, Key: key})
|
| Go already supports type elision in some places, such as...
| []someStruct{{Field: value}, {Field: value}}
|
| instead of having to type
| []someStruct{someStruct{Field: value}, someStruct{Field:
| value}}
|
| which would be equally pointless repetition.
|
| [0]: https://docs.aws.amazon.com/sdk-for-
| go/api/service/s3/#S3.Ge...
| qaq wrote:
| Yep exactly this ^^ thank you for providing great context
| that I should've added to the comment in the first place!
| morelisp wrote:
| I would also love this. The current rules for when you're
| allowed to elide are nonsensical.
| masklinn wrote:
| Ah yes, that does make sense, I would agree. That Rust
| doesn't have that (in any context that I know of) is one of
| its annoyances,
| itgkbcdfhb wrote:
| I want golang to stay as minimal as possible. I think of go as
| 2020s version of C. If you want all the madness of templating,
| reflection and (arguably) needless features can some privileged
| PhD student out there please make 2020s C++... go++?
| mountainriver wrote:
| I would really love to see default parameters and struct values.
| I jump between Python and Go in my day job and Go is a much
| better language overall but things like this make it painful
| geenat wrote:
| This is also my #1.
|
| Parameter / Option ergonomics.
|
| The current best practice of "functional options" and long
| function chains results in far too many function stubs ... its
| a minimum of 3 extra lines per parameter. Parameter structs
| require a whole extra struct...
|
| Borrowing optional / named parameters from Python would cut
| down length and complexity of Go code drastically.
| Jeff_Brown wrote:
| Default parameters have always struck me as dangerous. (And for
| my day job I use Python.)
| da39a3ee wrote:
| Can someone explain this one? I couldn't see why go gave this
| result.
|
| > What is the value of cp? If you said [A B C], sadly you are
| incorrect. The value of cp is actually: [C C C]
| masklinn wrote:
| The "trap" of the snippet is that `cp` is an array of
| _pointers_.
|
| What it shows is that Go doesn't have a `value` per iteration,
| it has a single `value` for the entire loop which it updates
| for each iteration. This means if you store a pointer to _that_
| , you're going to store a pointer to the loop variable which
| gets updated, and thus at the end of the loop you'll have
| stored a bunch of pointers to the last item.
|
| This is most commonly an issue when creating a closure inside a
| loop, as the closure closes over _the binding_ , and since Go
| has a single binding for the entire loop all the closures will
| get the same value.
| lanstin wrote:
| This has suprised me twice, once in my own code where i ended
| up ot really understand the problem but just mutated the code
| till it worked and then later when I was helping someone with
| their code and the way they structured the question made the
| still suprising answer memorable.
|
| A fix wouldn't be unwelcome but it seems it would have a good
| chance to cause performance regression - a lot more allocated
| values maybe on a lot of inner loops. I guess escape analysis
| might help avoid the allovations in the general case. ?
| masklinn wrote:
| > A fix wouldn't be unwelcome but it seems it would have a
| good chance to cause performance regression - a lot more
| allocated values maybe on a lot of inner loops. I guess
| escape analysis might help avoid the allovations in the
| general case. ?
|
| It seems unlikely unless the code is already incorrect (aka
| you're closing over or otherwise leaking the iteration
| variable).
|
| But regardless of how I dislike the current loop's scoping
| this is a significant semantics change so it would
| obviously have to be opt-in (and it would hopefully come
| alongside making range loops less crummy e.g. with an
| actual iterator interface).
| makapuf wrote:
| in the loop, you set cp[i] to a reference to the variable
| value. value is the same variable through the loop, with
| different values copied inside it, first A then B then C. So at
| the end you have cp having three times a reference to value,
| with the last value in it, namely C.
| tgv wrote:
| What would you need those for? For checking exhaustive type
| switches? Seems like an extra keyword on "interface" would do the
| trick.
| exdsq wrote:
| Is this comment meant for this article? I can't for the life of
| me grok what you mean!
| FreeFull wrote:
| Guessing it was meant to go on
| https://news.ycombinator.com/item?id=30205232 ? No idea how
| it ended up here
| dang wrote:
| We'll move it. Thanks!
| tgv wrote:
| Completely true. How it got here, no idea.
| [deleted]
| [deleted]
| synergy20 wrote:
| Most important items in my plate 1. a unified
| improved *error* in stdlib with stack trace support. 2. a
| unified log interface(mentioned) 3. a STL library like
| c++ 4. shared library support so we dont have 100 static
| binaries that among them each have 90% of duplicated content. go
| shall support shared libraries/modules officially.
| ternaryoperator wrote:
| Mine would be a much larger collections library. Having come to
| go from Java and finding that the standard go library has no
| trees, no stack, no skip list, etc. was quite a surprise.
| Possibly the advent of generics will stimulate development of a
| more robust standard collections library.
| christophilus wrote:
| It does have a doubly-linked list which can easily serve as a
| stack, though. And it has a heap which is a poor man's
| replacement for some uses of tree. I've found that I can get
| quite a bit farther with the built-in Go slices, maps, and
| lists than I thought.
|
| But yeah. Now that generics are in, I do hope they add a
| handful of common collections.
|
| [0] https://pkg.go.dev/container/list@go1.17.6
|
| [1] https://pkg.go.dev/container/heap@go1.17.6
|
| [2] https://pkg.go.dev/container/ring@go1.17.6
| sdevonoes wrote:
| I would add: an extended standard library for "common stuff". I
| don't want to import a third-party library nor write my own
| "utils.go" to do: func contains(s []int, e int)
| bool { for _, a := range s { if a ==
| e { return true }
| } return false }
| arccy wrote:
| soon-ish
| https://cs.opensource.google/go/x/exp/+/master:slices/slices...
| Mawr wrote:
| If you need to look up or worse, delete, a value in a slice,
| then you probably shouldn't be using a slice in the first
| place. You probably want to replace your slice with a set.
| rank0 wrote:
| I think Golang is awesome, but I have two major gripes that I
| hope can be fixed:
|
| Dependency management:
|
| Go mods is a dumpster fire. `go get` and `go install` is finicky
| and inconsistent across systems.
|
| It's difficult to import local code as a dependency. Using mods
| with replace feels like a shitty hack, and requires me to
| maintain a public repo for something I may not want to be public.
| I end up using ANOTHER hack that replaces mod references to
| private repos and I have to mess with my git config to properly
| authenticate.
|
| I've never used another language that made it so difficult to
| import local code. Rust's cargo is so much easier to use!
|
| Sane dynamic json parsing:
|
| Having to create a perfectly specified struct for every single
| json object I need to touch is terrible UX. Using
| `map[string]interface{}` is just gross.
|
| Again, I think Go should copy the existing rust solution from
| serde. With serde, I define the struct I need, and when I parse
| an object, the extra fields just get thrown out.
|
| If anyone thinks I'm misunderstanding something, please enlighten
| me. I hope reasonable solutions already exist and I just haven't
| found them yet.
| tyree731 wrote:
| > It's difficult to import local code as a dependency. Using
| mods with replace feels like a shitty hack, and requires me to
| maintain a public repo for something I may not want to be
| public. I end up using ANOTHER hack that replaces mod
| references to private repos and I have to mess with my git
| config to properly authenticate.
|
| With regards to this concern at least go 1.18 is adding
| workspaces, which should help (https://sebastian-
| holstein.de/post/2021-11-08-go-1.18-featur...).
| CamouflagedKiwi wrote:
| What you describe for JSON is already the case in Go; the
| stdlib json parser does simply throw out any extra fields on
| deserialisation.
| morelisp wrote:
| In fact it's difficult-to-impossible to get the opposite
| (only json.Decoder supports a strict mode, and as soon as one
| intermediate type implements UnmarshalJSON it stops getting
| propagated).
| maccard wrote:
| This is a great post, and I agree with much of what he said
| (range shouldn't copy - I would love a range that iterates by
| const-reference by default, to lift a phrase from C++).
|
| Deterministic select I hard disagree with. The code in the blog
| post is race-y, and needs to be fixed, not select. If anything,
| making select deterministic will introduce _more_ subtle bugs
| when developers rely on that behavior only to find out in the
| real world that things aren't necessarily as quick as they are in
| development.
| sethvargo wrote:
| How would you fix that code?
| schrodinger wrote:
| I think you could do which I'd argue is more idiomatic (
| for { if _, ok := <- doneCh; ok { break
| } select { case thing := <-thingCh:
| // ... long-running operation case
| <-time.After(5*time.Second): return
| fmt.Errorf("timeout") } }
|
| Which goes along w/ https://github.com/golang/go/wiki/CodeRev
| iewComments#indent-... of "Indent error flow".
|
| edit: nvm, your break would be blocked until one of the other
| channels produced a value. you'd need to check for the doneCh
| redundantly again in the select.
| assbuttbuttass wrote:
| I would write the author's example as follows:
| for ctx.Err() == nil { select { case
| <-ctx.Done(): return nil case
| thing := <-thingCh: // Process thing...
| case <-time.After(5*time.Second): return
| errors.New("timeout") } }
|
| The extra check for ctx.Err before the select statement
| easily resolves the author's issue.
| sethvargo wrote:
| It really doesn't though. It handles the case where the
| context might have expired or be cancelled, but there's
| still a race when entering the select between the
| ctx.Done() and reading from thingCh. You may end up
| processing one additional unit of work. In situations where
| the exit condition is channel-based, this won't work.
|
| Additionally, this would only work if you had one
| predominant condition and that condition was context-based.
| If you have multiple ordered conditions upon which you want
| to exit, I can't think of how you'd express that as a
| range.
| assbuttbuttass wrote:
| I'm not sure what you mean. There's _always_ going to be
| a race condition between ctx.Done and thingCh, just
| depending on whether there 's data available. This race
| condition is unavoidable.
|
| I guess you're thinking of "what if thingCh and ctx.Done
| activate simultaneously?"
|
| There's no real difference between happening
| simultaneously and happening one after another.
|
| As for your other point, you can just write code like
| select { case x := <-conditionA:
| return x default: } select {
| case x := <-conditionB: return x
| default: } ...
|
| But I've personally never needed code like this.
| sethvargo wrote:
| Right, which is noted in the post. That verbosity is,
| well, verbose. I generally need this in 20% of things I
| write.
| oconnor663 wrote:
| In this use case, is it bad if the Done signal arrives
| the instant after you check it?
| sethvargo wrote:
| The context was introduced by the commenter. The original
| post does not use contexts. In general, there's a pretty
| common set of patterns in which multiple goroutines are
| writing data to different channels, and you need to
| ensure the data from those channels are processed with
| some level of priority.
| assbuttbuttass wrote:
| Two channels is a poor way to handle priority. If data
| comes in on the low priority channel just before the high
| priority channel, you would still be blocked waiting for
| the low priority task to complete.
|
| In a case like this, maybe just run two different
| consumer routines for the two channels, then neither
| would be blocked waiting on the other.
| sethvargo wrote:
| Also, this isn't semantically correct. In order to ensure
| that `conditionaA` is _always_ preferred over
| `conditionB`, you must also check if `conditionA` has
| received a value inside of `conditionB`:
| select { case a := <-conditionA:
| return a default: } select {
| case b := <-conditionB: case a := <-conditionA:
| return a default: return b
| default: }
| assbuttbuttass wrote:
| It would be easier to discuss with a more concrete
| example. If I ever had to write code like the above I
| would reconsider the design and try to come up with
| something simpler.
| morelisp wrote:
| I was also curious so I picked a likely-looking project
| on his github and indeed found an attempt to handle a
| channel "deterministically" at
| https://github.com/sethvargo/go-
| retry/blob/main/retry.go#L51
|
| Honestly the whole first select seems redundant; any code
| that relies on this is broken as there's no other
| synchronization points to hang onto. You simply can't
| pretend the clock on the wall has anything to with the
| values transiting the program unless you introduce an
| actual synchronization point.
|
| But OK, maybe you do have some strange performance case
| where this matters? In that case the whole thing could be
| more succinctly solved by looping on `for ctx.Err() ==
| nil` instead of infinitely. Exactly as suggested at the
| start of the thread. (This would also likely be faster
| unless the context is under _massive_ contention.)
|
| It also leaks the timer until it fires if the context
| cancels, which seems like it would be more of a practical
| performance problem than any overhead to the additional
| select.
| morelisp wrote:
| Context cancellation propagates (potentially)
| asynchronously anyway, so if you're relying on something
| canceling your context and that immediately appearing you
| already have a bug.
|
| I've written `select { ..., default: }` enough times I
| also wish it had shorthand syntax - sometimes it's even
| clearer to range one "primary" channel and lead the code
| block with that check - but I cannot think of a case
| where relying on a deterministic select would not have
| led to a bug.
| vasergen wrote:
| Not go developer, just curious about the language and ecosystem
| and really like it. For me would be nice to have more functional
| features. For example - explicitly say that a variable is mutable
| / immutable, preferably have immutability by default. Also native
| support for map/filter/reduce/etc. Those are good abstraction and
| it is easier to read than `for loops`, since you don't need to
| look over shoulders all the time. Guess latest would be easier to
| add since there is support for generics already.
| jerf wrote:
| "Also native support for map/filter/reduce/etc."
|
| Native support for them in the context of the existing Go spec
| will be coming with the next release. To reserve the right to
| evolve the native support before committing it to the backwards
| compatibility promise, it will initially appear in the
| https://pkg.go.dev/golang.org/x/exp external repository, but
| that is the official Go repo for things either too unstable to
| be included in the standard library, or still experimental.
| General expectation is it'll be in the standard library in the
| release after that. It won't be in the standard library, but
| it's as official as it can be beyond that.
|
| I carefully phrased that with "in the context of the existing
| Go spec", because I think expectations of this support are
| wildly out of whack with the reality. It's still going to be a
| very unpleasant style to work in, with many and manifold
| problems: http://www.jerf.org/iri/post/2955 . I think people
| will be crazy to turn to that style in Go. Go wasn't just
| missing generics to support this style, it was missing _many_
| things, and "solving" the generics problem still leaves it
| missing many things.
| randallsquared wrote:
| The issue with the order of range seems like using the same name
| for satisfying a different requirement: in a template, you're
| much more likely to want the value than the index, so it makes
| sense that a looping construct with a single parameter would be
| putting the value in that parameter. In a loop in normal code,
| you're more likely to want to do math on the index. So, I'd say
| the problem is more about punning the name of these two behaviors
| than the behavior itself being bad.
| masklinn wrote:
| > In a loop in normal code, you're more likely to want to do
| math on the index.
|
| It really is not, no. The number of loops using `enumerate` (or
| working on range / indices directly) in Python or Rust are a
| small fraction of those just iterating the sequence itself.
|
| That would be even more so for Go, which has no higher-level
| data-oriented utilities (e.g. HOFs or comprehensions, which
| would usually replace a number of non-indexed loops, and would
| thus increase the ratio of indexed to non-indexed loops).
| JulianMorrison wrote:
| > Alternatively, Go 2.0 could implement "frozen" global variables
|
| A more general change would be to implement the "var" and "val"
| distinction that exists in some languages.
| const x = 1 // x is a compile time alias for the untyped abstract
| number 1 var x := 1 // define x at runtime to be (int)1,
| x is mutable val x := 1 // define x at runtime to be
| (int)1, x is immutable
|
| Then the globals can be defined with "val".
| bruce343434 wrote:
| the val and const cases should hardly be different if the
| compiler has constant folding, except maybe for the typing.
| JulianMorrison wrote:
| From the language reference
|
| > Constant expressions may contain only constant operands and
| are evaluated at compile time.
|
| and a "const" can only be defined with a constant expression.
|
| So the difference would be that a "val" can be assigned a
| value that is evaluated at runtime.
| tapirl wrote:
| No need to invent a new keyword, it is ok to just use "const":
| https://github.com/go101/go101/wiki/An-immutable-value-propo...
| JulianMorrison wrote:
| No, they are very different conceptually.
|
| - A const is an abstracted value.
|
| - A variable is an allocated piece of memory.
| mikepurvis wrote:
| Isn't that an implementation choice?
|
| Like in C++, a const is absolutely allocated since you can
| get a pointer to one. And then you can do horrible stuff
| like const_cast that pointer and mutate the value, and the
| possibility of that occurring prevents the compiler from
| doing certain const-related optimizations.
| catlifeonmars wrote:
| > Like in C++, a const is absolutely allocated since you
| can get a pointer to one.
|
| If I understand you correctly, you claim you can get a
| pointer to a Go const. This is not the case. For example,
| the following code will not compile:
|
| const a int = 1
|
| var b *int = &a
|
| ./prog.go:5:15: cannot take the address of a
|
| See https://go.dev/play/p/QPxP-tF6qIs for a live example.
| mikepurvis wrote:
| I'm not an active golang user, so it wasn't a comment on
| that, more just on the GP's assertion that a const had to
| be an abstracted value. It sounds like that is indeed the
| case in Go (an implementation choice), but more broadly,
| I wouldn't expect "const" to mean anything more to most
| programmers than "give me an error if I or anyone else
| try to modify this thing."
|
| And in C++, it certainly isn't. Even a constexpr in C++
| is a thing you can get a pointer-to-- C++'s only
| guarantee with a constexpr is that it has to be possible
| to evaluate it at compile time.
| catlifeonmars wrote:
| Ah gotcha. Makes sense. The way I typically think of Go
| const is more akin to a typed macro, rather than a first
| class value reference where it is expanded in place.
| Notably, in Go constants cannot contain structs, arrays,
| so they have much less to do with mutability than in,
| say, in C++.
| JulianMorrison wrote:
| Yes, it is an implementation choice. I feel it's a choice
| that makes sense, too. But regardless, it's how things
| work right now in Go.
| tapirl wrote:
| Concepts are defined as needed.
| JulianMorrison wrote:
| That wasn't exactly what I meant. What I meant was
|
| - as of right now they are different, and clearly
| distinct, and it's actually important to unlearn thinking
| of a const as a var, because they don't do the same thing
| in practical terms
|
| - that proposal would muddy the distinction.
| tapirl wrote:
| Yes, they are different, const values doesn't allocated
| in memory NOW. But who cares? Most gophers just think
| const values are immutable values.
|
| I mean we could let some const values allocated in
| memory.
| JulianMorrison wrote:
| Thankfully the people who make Go do care about
| conceptual muddles.
| avgcorrection wrote:
| I don't understand why Scala chose "var" for mutable variables.
| A variable is not defined by being mutable--it is defined by
| being variable, i.e. not a constant. And it is immutable in
| math (where we don't have to care about performance). So "val"
| is also a "var", conceptually.
| JulianMorrison wrote:
| I think it was Fortran which introduced the equivalence
| between programming variables (that are a mutable piece of
| memory) and mathematical variables (which describe a
| relationship). But they aren't really the same. And
| "variable" has become too fixed in computer science usage to
| be repaired back to its earlier mathematical meaning.
| bruce343434 wrote:
| if it is not constant, it must be variable i.e. changing.
| Mutation = change.
| ackfoobar wrote:
| def plusOne(x: Int) { val y = x + 1; return y }
|
| In the line of code above, `y` is an immutable variable. It
| does not mutate, yet it "varies" as different values of x
| come in.
| avgcorrection wrote:
| No. A variable in mathematics does not change (mutate). And
| yet it is not a constant.
|
| You're thinking inside the imperative programming box.
| maerF0x0 wrote:
| could be interesting, however I'd hope for something more
| visually distinctive that val/var as it took about 2-3 reads
| for me notice what was even the diff between L2 and L3.
| JulianMorrison wrote:
| Fair point. "value" and "var" then, perhaps?
| maerF0x0 wrote:
| perhaps, the real details would get hammered out in a
| proposal.
| catlifeonmars wrote:
| How about `mut var`?
|
| Edit: this likely wouldn't fly as it would be completely
| backwards incompatible
___________________________________________________________________
(page generated 2022-02-04 23:01 UTC)