Live data from Hacker News

Go Replaces Interface{} with 'Any'

github.com

411–420 of 481 posts

Re: Go Replaces Interface{} with 'Any'

#411
post #305

Earlier quoted context omitted.

Well then I guess it's Java for you until retirement. Go can feel a bit alien if you only know "C-style" languages, because its creators took inspiration from a wide array of languages, including Pascal. Actually Pascal-style declarations are easier to read, but you have to keep an open mind and not just go "doesn't look like what I'm used to, so it must be bad"...

C/Java style of annotating the return type of a function first, and then annotating the argument types before the name feels really old school at this point. Python, TypeScript, Go, Rust, etc. all opted for annotating after the name. Since this is so prevalent in newer languages, despite a pretty strong tradition in the other direction, I wonder if there is a pretty good reason for this which language design experts…

> C/Java style of annotating the return type of a function first, and then annotating the argument types before the name feels really old school at this point. Python, TypeScript, Go, Rust, etc. all opted for annotating after the name.

>

> Since this is so prevalent in newer languages, despite a pretty strong tradition in the other direction, I wonder if there is a pretty good reason for this which language design experts are keenly aware of when they design new languages.

My $0.02:

Maybe consistency between named functions and anonymous functions?

If you put the return type of a function before the name then it reads ambiguously when the name is left out (as in anonymous functions):

// Named function

int funcName (params) { ... }

// No-name function

int (params) { ... }

Which leads to the language needing alternative syntax or extra keywords when declaring anonymous functions:

// Something like this maybe?

int lambda (params) { ... }

If you put the return type after the function information but before the body then it's always consistent:

// Named function

funcName (params) : int { ... }

// No-name function

(params) : int { ... }

And, of course, to retain consistency you then make sure that all variables are declared the same way (type following variable name):

// Var declaration

myvar : int;

The disambiguation comes into its own when creating functions inline:

// Prefixed return-type looks odd

callFooWithFunc (int (argslist) { ... });

// Prefixed return-type requires extra keywords to not look odd

callFooWithFunc (int lambda (argslist) { ... });

// Suffixed return-type looks normal

callFooWithFunc ((argslist) : int { ... });

Re: Go Replaces Interface{} with 'Any'

#412
post #330

Earlier quoted context omitted.

> I don't agree. I usually don't care so much when a particular feature was introduced into a language I care very much when a feature was introduced into a language, because maintaining compatibility with earlier versions of the language determines what features may be used. If I'm working on a library that needs to be compatible with C++03, then that means avoiding smart pointers and rvalues. If I'm working on a li…

> In order to determine it for X > Y (new code on old compiler), you need to know when features were introduced. I think this is a deliberate reduction of dimensionality. Go says that you don't need to worry (for long) about this case, because the toolchain must be updated regularly - and promises that it will be as pain free as possible. This simplifies for the Go team, for library authors, and library users in most…

> Not saying this tradeoff is for everyone, and I've never used C++ professionally so I'm probably ignorant. But are you saying it's common with production projects that use a compiler from 2003 or earlier? What's the use case?

The first difference is that there isn't just a single compiler, but rather a standard that gets implemented by different compiler vendors. It's gotten better since then, but typically it would be a while between the updated standard being released and the standard being supported by most compilers. (And even then, some compilers might not support everything in the same way. For example, two-phase lookup was added in C++03, but MSVC didn't correctly handle it until 2017 [0].)

The second difference is that the C++ compiler may be tightly coupled to the operating system, and the glibc version used by the operating system. Go avoids this by statically compiling everything, but that comes with its own mess of security problems. (e.g. When HeartBleed came out, the only update needed was for libopenssl.so. If a similar issue occurred in statically compiled code, every single executable that used the library would need to be updated.) So in many cases, in order to support an OS, you need to support the OS-provided compiler version [1].

As an example, physics labs, because that's where I have some experience. Labs tend to be pretty conservative about OS upgrades, because nobody wants to hear that the expensive equipment can't be run because somebody changes the OS. So, "Scientific Linux" is frequently used, based on RHEL, and used up until the tail end of the life-cycle. RHEL6 was in production use until Dec. of 2020, and is still in extended support. It provides gcc 4.4, which was released in 2009. Now, gcc 4.4 did support some parts of early drafts of C++11 (optimistically known at the time as C++0x), but didn't have full support due to lack of a time machine.

So when I was writing a library for use in data analysis, I needed to know the language and stdlib feature support in a compiler released a decade earlier, and typically stay within the features of the standard from almost two decades earlier.

[0] https://devblogs.microsoft.com/cppblog/two-phase-name-lookup...

[1] You can have non-OS compilers, but then you may need to recompile all of your dependencies rather using the package manager's version, keep track of separate glibc versions using RPATH or LD_LIBRARY_PATH, and make sure to distribute those alongside your library. It's not hard for a single program, but it's a big step to ask users of a library to make.

Re: Go Replaces Interface{} with 'Any'

#413
post #352
post #78

Earlier quoted context omitted.

People downvote it, but it's true. 2/3 of your Go code is `if err` blocks. And the way you have to chain the error messages to make the stack make any kind of sense is just maddening.

We often have errors handled. In contrast to many Java codes just spitting unhandled exception stacktraces or crashing completely.

You must handle an exception in a language like Java. Yes, you can just print the stacktrace, but that is a choice. I don't HAVE to handle errors in Go until the program crashes as well. I never really understood that argument.

Re: Go Replaces Interface{} with 'Any'

#414

Earlier quoted context omitted.

Why don't you list the languages that you have used? Otherwise there isn't really any new information. For example for, having done Java, Scala, Python, Groovy, Haskell, Typescript and a couple others, Go reads extremely horrible. It feels as bad as enterprisey Java to me.

The readability of Go is intrinsically linked to the error handling in Go. If the spec started accepting "clever" rules to do with auto-magic return / assignment of error values instead of explicit handling, then the cognitive load of code review increases. If every error path is explicit, then the review becomes simple and self-explanatory (at least when it comes to error handling).

I'm not sure error handling in Go is really explicit. Go checks that you assign an error and usually handle it, but it doesn't check that you handle all values of the error. If a function suddenly returns a new error value, the compiler won't help you here. This is like in language with unchecked exceptions, you have to read everything carefully.

Re: Go Replaces Interface{} with 'Any'

#415
post #73
post #65

Their code is full of things like: 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 /* */ values; Map /* */ map; Some devs spent a whole week doing nothing other than removing those commented out generic type decl…

> Some devs spent a whole week doing nothing other than removing those commented out generic type declarations once Java finally got generics! Fast-forwarding to today, could Co-pilot have saved them the trouble?

I'm not sure about Copilot but IntelliJ IDEA's structural search and replace[0] is well suited to this task.

[0] https://www.jetbrains.com/help/idea/structural-search-and-re...

Re: Go Replaces Interface{} with 'Any'

#416

gofmt -w -r 'interface{} -> any' src Is this a real command? If so, I’m very impressed. Is there any equivalent for c++ and other languages?

IntelliJ, Resharper, and presumably Rider have structural search and replace[0]. It's available for at least C#, Java, and Kotlin.

[0] https://www.jetbrains.com/help/idea/structural-search-and-re...

Re: Go Replaces Interface{} with 'Any'

#417
post #39

Earlier quoted context omitted.

In semantic versioning, going from 1.X to 2.X is only indicated when there are incompatible API changes. Adding generics doesn’t break compatibility with preexisting Go code, so it’s unnecessary to increment the major version.

Sure, but semantic versioning really is the wrong kind of versioning to use for a language. The major version should represent major language changes, not whether its a breaking change or not, semantic versioning isn't somehow magically a "good" way to version. It's useful for libraries / dependencies where you are dealing with many different libraries and just want to know you can upgrade without having to deal with…

I think we should go back to years (for most software, in fact, not just languages). Languages change slowly enough (or should) that, e.g. "Ada 2021" should be unambiguous enough. For language implementation versions, then we can add semantic numbers afterwards.

Re: Go Replaces Interface{} with 'Any'

#418
post #254
post #189

Earlier quoted context omitted.

> Sure, but semantic versioning really is the wrong kind of versioning to use for a language. I don't agree. I usually don't care so much when a particular feature was introduced into a language (and if I do, it's usually a Wikipedia search away). I mostly care whether or not code written assuming version X can be compiled with version Y of the compiler. Semantic versioning can tell me the latter. Making versioning a…

You could separate the semantic version from the PR version. The PR version doesn't even have to be numeric. You can give them proper names.

Didn't Java try that? I believe it was Java 1.5 that was originally marketed as Java 5.

It was confusing.

Re: Go Replaces Interface{} with 'Any'

#419
post #206

Earlier quoted context omitted.

> Java (and Javascript in its attempt to copy it) are about the only languages that actually promote using exceptions for errors Python does. Ruby does. It's not just Java and JS. Go is very open about its approach being a departure. > And actually, many APIs in the wild do represent errors as integers. Many, many APIs in the wild are implemented in (or meant to be consumed from) C, which doesn't even have exceptions…

> Python does And it's awful. I use EAFP locally (to avoid TOCTOU and the like) at low level interfaces but I don't let it bubble up out of a function scope, because it is a goto in all but name. I've also been increasingly using the `result` library/data structure. It's incredibly liberating to return an error as an object you can compose into other functions, vs try/catch, which does not compose. Yes I write python…

Maybe I'm just too inexperienced, but I don't see the point of this library.

The linked example shows a really basic sqlalchemy model lookup. What does spewing these new types all over my code get me that returning None or an empty dict/list doesn't without the overhead?

    def find_user(user_id: int) -> Optional[User]:
      user = User.objects.filter(id=user_id)
      if user.exists():
        return user[0]
      else:
        return None
Not only is this idiomatic, it conveys the same semantic meaning. I'm using an IDE, as is anyone else working in a large codebase. I'll be told at the point of invocation that find_user could return None and I need to possibly deal with that.

Re: Go Replaces Interface{} with 'Any'

#420

Earlier quoted context omitted.

I've used all of those and more and spent a decade deep in the purely functional Scala/Haskell camp. Diving into a moderate or large Go code base is always far easier for me even though I've used Go much less than the others. Before I even knew Go I would often look at various algorithms in Go just because it was so easy to understand exactly what was happening.

For algorithms, this might indeed be different. Not only are algorithms usually small and very focussed snippets, the emphasis is on performance and hence mutability, language primitives and shortcuts are common. I can definitely see that languages like python or go beat pure functional languages in that regard. For business logic and glue code (which in my field is the vast amount of code) I think it is the opposite…

Go is easier to read for complex code bases.
Post reply on HN