Edit: I’m impressed generics aren’t a breaking change! I thought this changed Interface{} to Any as a breaking change
It's much much more useful to the users to say, 2.0 introduced generics, it's distinct. If it's like other languages, generics changes the code people generate a lot, libraries start looking significantly different. It's very distinct, and if that is simply in version 1.18.0 or whatever, that is super bad usability from a language perspective.
Basically, if 1.18 code is extremely unlikely to work against a 1.17 compiler, because a new (technically additive) feature is pervasively threaded through new code, I feel like it's hard to describe them as part of the same epoch.
I don't write enough go to know if that's true for generics, but it seems like it could become true fairly quickly from my experience with other languages.
A language is an interface with humans. Switching to generics is a major change in the way to think about the source code. It's not an implementation detail which is not a big deal as long as the compiler can accept earlier source syntax.
For the release announcement: https://news.ycombinator.com/item?id=29556646
So an unbounded type parameter is [T interface{}], or [T any] when using the shorter alias. It's a type alias / predeclared identifier defined in the universe scope:
type any = interface{}I never jumped the Go bandwagon because of the lack of generics. Can I now try Go?
The following article seems to say that the lack of other features can be frustrating (the lack of lambdas and the lack of the functional handling of collections seems problematic to me)
Also I wonder whether the language has a IDE support as good as IntelliJ with Java (safe function extraction, type-safe autocomplete)
Would the local gurus here please comment?
[the idea for me is to replace my tricky shell scripts with Go scripts.]
https://medium.com/webstep/a-java-developers-adventures-thro...
(In fact, generics have been a relatively late and reluctant addition to Java. They had been at home in ML-family languages and others for ages before.)
Go seems amazing otherwise, but I cannot get over the fact that it forces me to capitalize or not capitalize my variables, and has types in the wrong order.
Go often works well for replacing shell scripts, even without generics.
That’s selling the language short. Way too short.
Probably no. You still would be very disappointed. Go's take at writing and maintaining software often is pretty repugnant to people with a strong Java or JavaScript mindset. If missing user defined parametric polymorphism ("generics") was a reason to not even _try_ it you will be offended by by other things Go does in its particular way. Be it error handling, pattern matching, concurrency, mutability, etc. Basically if you try to write Java (or JavaScript) programs in Go you will suffer and hate Go. Same for C++/Rust aficionados. Go's newly added "generics" still come without "library support".
I'm mostly a C# developer that uses a ton of generics and when I tried Go previously I was disappointed that it didn't have generics. But I continued on. And so I found a handful of other things that would irritate me or would be an inconvenience versus doing the same thing in C#. So generics alone is not the main problem here, it is going from a C#/Java mindset to a Go mindset. In some way we would probably get bored with Go because it is so simple/easy and not much to mess up, versus the super complex object empires in C#/Java land.
So the problem is not really a problem, go is just a different tool for a different kind of problem or if your brain works in a specific way - but I wouldn't call it a problem at all. In a way, go is what Buddhism is to other (more fully featured) religions. I think if you get proficient with you can probably have a very peaceful programming experience and not fight against the system (like in Java, half the battle is just battling the tooling).
Thanks for bringing this up, will give Go another look again, its been a while!
That said, generics are still new and won't have the strongest library support for a while, and it takes a bit for idioms to settle. It wouldn't be unreasonable to wait for ~a year.
And eg Go's old workaround for polymorphic sorting functions was just atrocious: it was rather convoluted, and only really worked at all for in-place sorting.
Jet Brains makes GoLand, which is about as good as they come. It should feel familiar if you're used to IntelliJ. https://www.jetbrains.com/go/
[]interface{}{1, 2.0, "hi"} -> []any{1, 2.0, "hi"}
Now that Go is going to have generics, all we need is sugar syntax for early return on error -- like more modern languages such as Rust and Zig have -- and Go may finally be pleasant to program in!An empty interface can represent any type because every type inherently implements an interface with no methods. And that's what Go is all about -- implicitly implementing interfaces.
If a newbie hops into Go and just starts using "any" I think they might assume it's a magic type that's at the base of everything, missing out on the fact that they're still taking advantage of interfaces.
Edit: It’s also one of the most approachable languages.
It makes me a bit sad to read that. We all are on a journey to be become better developers every day. But things like that are like a distraction. They make you think you found a really cool and smart concept, but you actually didn't and it's essentially just a hack.
Not sure what the solution can be. But PL designers should be more clear about features that are just "hacks" and considered "bad" but necessary in practice due to certain constraints. Go's {} and nil-errorhandling are examples. Nulls (in any language) are another common example.
Let's see how this plays out.
There's a few other languages out there that work that way. The split is probably is probably 20/80. Though "popular" languages heavily learn towards Java-esque interfaces - with TypeScript and Go being the odd ones in that bunch.
Having lots of syntactic sugar works out OK for Haskell.
For example, in Haskell straight-line imperative looking code is an alias for some underlying callback hell.
The state of error handling in Go at the moment is embarrassing at best.
type Result[T] struct { ok: *T err: error }
Great, so how do you work with it?
* You can have a `ok()` getter that returns `*T`. Now you you need an `if x != nil`
* `isOk()` + `isErr()`
* `unwrap() T`, which panics on errors
* `split() (*T, err)` that splits into separate values, especially awkward since you both need `if err != nil` AND dereference the pointer
That API is more awkward then the status quo, and doesn't buy you any correctness guarantees because eventually you have to do an `if x := res.ok(); x != nil` or `x, err := res.split(); if err != nil {` anyway.
Pretty much the only convenience you gain are functions like `map()` or `unwrap_or`, but eventually you always have to take out the value and without being able to pattern match you can't get an API that improves correctness much.
(std::optional in C++ is a great example)
- map / map_err
- and_then / or_else
- unwrap / unwrap_or
And countless of other functions making it very practical to chain computations without having to pattern match anything.I do the same in Erlang/Elixir.
In Golang, I need to check every function call, and if I want to know where an error come from, I need to wrap it in an errors.New() because no exceptions = no stacktrace
Emphasis on eventually instead of at every single function call.
No. You can just feed it to the function (from a library / stdlib) that needs it, or call .fold() in the end.
that's similar to what Java does with the Optional type, not great, but not bad either
the alternative is checking for nulls which is worse in any possibile way
I usually implement something like Kotlin Result when I have to code in Java
with a couple of static helpers to build the result: Result.success(T) Result.failure(Throwable t)
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/
also Result in Rust has been an inspiration
Seems to me that the ones who write about "idiomatic go code" aren't the ones who are shipping softwares/libraries.
People makes it a big deal, in reality it's not.
I rest my case.
may_fail_who_knows()
.map(use_value)
.map_err(some_error_processing)
.and_then(another_computation_which_can_fail)
.or_else(with_some_error_handling_that_can_rescue)
.unwrap_or(a_default_value)
Basically, instead of nested match expressions, you get a "pipeline".Can you provide an example? What's wrong with `return err`? That's a very explicit early return on error, no magic or language features (= added complexity) needed.
The baseline is that constraints aren’t best expressed as interface literals in situ. Unlike exceptional use of ‘interface{}’, ‘any’ will be a more naturally invoked constraint.
Also, some of the uses of constraints rely on a unification involving an ‘any’ term that cancels out. Here, the use of ‘interface{}’ is not incorrect but maybe indirect.
func doSomething[X interface{}, Y Fooer[interface{}]](v X, src Y) error {
}
versus: func doSomething[X any, Y Fooer[any]](v X, src Y) error {
}
the signatures can get long pretty quickly when you have more than one type parameter. type any = interface{}
not work, if you felt that strongly?type Any interface{}
?
`type any interface{}` would declare a new type, while an alias is exactly another name for the same type.
type any = interface{}
That means they're interchangeable compared to defining a new type. type Any = interface{}
That is, it's an alias and casting is not necessary.That would get rid of the "panic: runtime error: invalid memory address or nil pointer dereference" errors.
https://wakatime.com/blog/48-go-desperately-needs-nil-safe-t...
… and pattern matching. Maybe just some extensions for `switch`.
… and one of the `try` proposals.
That having been said… I do appreciate that Go has gotten where it is today by being radically simple, and that a lot of extreme care needs to be done to add new features to the language. It’s hard to draw a firm line in the sand. I feel like all of these features would work great together, though; it’d enable Go to do something like `Result` in Rust with few language-level changes.
I even remain somewhat skeptical about generics, but I am hopeful.
Go's proponents think it's okay to be a bit less powerful so that there is only 1 maybe 2 ways to do something. This makes code at different companies more similar.
It also mean that a new grad can join the team, read the code, copy it, modify it some, and it's pretty much right. A staff SWE and a new grad will right very similar code in Go. In C++ it's anyone's guess how similar their code will be.
> … and pattern matching. Maybe just some extensions for `switch`.
Go has type switches which are… ok. If it were possible to “close up” interfaces and type switches took that in account (match completeness) you’d be done about done, you would not have the structural / patterned unpacking but that’s probably less of a concern.
I don’t hate Scala and I’ve even written a few PRs in Scala, but I have had trouble picking it up for actual projects.
Go is extremely pragmatic and often favors clarity in how code will execute and simplicity in syntax and grammar. It’s basically a GC’d successor to C in many regards. It eschews classes for interface-based polymorphism, it compiles and runs code very fast, and above all, I’ve found it easy and rewarding to pick up.
I don’t want all of the expressiveness of Scala. Just a bit more than Go has today. Not much more.
But that's already possible, just declare arguments and return values as values instead of references. I mean you'll get the zero value if you don't use those functions properly, but those won't cause nil pointer dereference errors.
I mean, it's a tradeoff between performance and developer competency. To be harsh, I think nil pointer dereferences are developer error, not a flaw in the language.
There is also a tutorial on generics: https://go.dev/doc/tutorial/generics
https://www.makeworld.space/2020/11/go_modules.html
Looking for a tutorial on generics myself.
The normie PL bar is rising, slowly but surely.
Is this a real command? If so, I’m very impressed. Is there any equivalent for c++ and other languages?
The combination of unambiguous syntax and consistently-formatted code results in rewrites producing meaningful diffs (most of the time).
[1]: https://pkg.go.dev/cmd/go#hdr-Update_packages_to_use_new_API...
[0] https://www.jetbrains.com/help/idea/structural-search-and-re...
func foo(x interface{String() string}) string {
return x.String()
}Same for an old Android phone with outdated Snapdragon 660 I use to test app performance bottlenecks. Except this one took 4 secs to load.
Oh, and maybe this goes against the Go ethos, but Python has a library called Sympy for Computer Algebra, and it's quite nice. It clearly relies on operator overloading.
I'm guessing this won't happen for a long time because operator overloading has a reputation (rightly or wrongly) for being abused. Oh well!
[edit]
More philosophical point: Overloading in programming started with mathematical operators, no? The motivation for having it as a language feature is very clear (if you know enough mathematics). What happened?
People have written programs in both of those areas using Fortran-IV which doesn't have overloading either.
I suppose I've fed the troll.
I also like that Go is fast. It appears that some Python code is being rewritten in Go to make it go faster. Python's slowness is a problem in both the areas I've mentioned, and I know the usual workarounds.
One of the reasons golang is so simple is that they move very carefully on issues like this. It has its benefits and its drawbacks.
Adding operator overloading kind of opens a can of worms here. If they add that, why not arithmetic types? Why not exceptions? Why not x, y, z... Everyone wants just _one_ extra feature that would improve the language. Trouble is everyone disagrees on what that one feature should be.
[1] Cornelius Diekmann, “Looping with Untyped Lambda Calculus in Python and Go,” Paged Out!, vol. 2, p. 22, Nov. 2019. https://pagedout.institute/ https://www.net.in.tum.de/fileadmin/bibtex/publications/pape...
I like Typescript a lot but the reality of Typescript development is not great.
Of course, Go makes zero attempt to enforce nullability (nilability?) in its type system... so... win some, lose some I guess.
If there is, I imagine it's not among the type of people who you'd want leading technical decisions at your company.
Also, application servers are cheap, persistent, fast and durable storage isn't, these won't be written in TS.
TL;DR: different problem domains, not important
You mean a reserved keyword? No, 'any' is not a keyword, it's an identifier (a predeclared identifier). The usual shadowing rules apply.
But having `any` as syntactic sugar for that concept is a good idea.
struct{} -> ?
type fileOps []any // []T where T is (string | int64)
Go does not have neither generics nor union types. So people have to do this kind of thing :( I feel sorry for them.Reminds of Java 4 (15 years ago or something) where code was full of this crap:
List /* <String> */ values;
Map /* <String, Object> */ map;
Some devs spent a whole week doing nothing other than removing those commented out generic type declarations once Java finally got generics!It's really interesting you used those as your examples because that need was fully met in a type-safe way already, and other examples are actually much rarer to come by.
Trying to counter argue against an example by using the actual example, rather than the point the example is trying to convey, is a common, but quite dishonest debate strategy.
If I see a map or slice I know exactly how it behaves, how much memory it consumes, what the runtime behavior is. If you make those generics instead then I have literally no fucking clue what's going on.
Fast-forwarding to today, could Co-pilot have saved them the trouble?
[0] https://www.jetbrains.com/help/idea/structural-search-and-re...
Oh good grief. There are 32,768 programming languages. People are free to pick the ones they want to use and nobody wants your pity.
In Pascal, you can’t break out of a for statement, the limit and step value are evaluated only at start of the loop, and you can’t change the value of the variable being looped over.
Consequently, the number of iterations through the loop taken is known when the loop gets entered.
I'd imagine you can continue using interface{} for the foreseeable future.
for sum < 1000 { sum += sum }
Not a bad move, though. Most Go "Interface" things do tend to be "{}"