One thing I think generics in Go is missing is the <?> concept in Java.
If you're taking a List[T] and all you want to do is to call list.size() then you don't care what type of list it is. In Java you can write a function which takes a List<?> but in Go you have to write List[T] so then the question becomes what is T?
You have to make the function (or type you're a method on) generic. If you make the type generic then every user of your type also needs to specify T, etc.
I don't think it would be impossible to add that to Go. Allow List[?], which matches a List with any type parameter. Calling functions which don't involve the type parameter like list.size() would be fine, calling a method returning the type parameter like list.get(n) would return "any", and methods taking the type parameter like list.set(n, obj) would probably not be callable.
Go Generics work differently than those in Java. They are specialized, meaning that they are not generic at runtime anymore. Instead, the compiler creates a different implementation for each type.
At runtime, there are only List[int], List[string], etc. List[T] is not a thing anymore.
The one thing I hated about Java's generics was type erasure. It worked for the JVM but it had such stupid limits, like not being able to switch on a type, like go. (interfaces are types, this is exploitable)
You need this in Java because interfaces are explicitly implemented. You don't need this in Go because interfaces are structurally implemented. The way you spell "anything that has a method named Size which returns int" is interface{Size() int}.
> What people want generic methods for is to do deeply nested call chains
I don't see that as the only use of generic methods.
The example in the article is a "Map" method that transforms e.g. a List[A] to a List[B], by taking a function that takes an A and returns a B. To be able to transform a list like that is a useful operation.
It was possible to do the same with a global function like MapList but the syntax is nicer if you use methods. You don't need the type in the name (function MapList vs method Map) and it is an operation on the List after all so list.Map(..) is nicer than MapList(list, ..).
People advocate for Go's error handling because it forces you to deal with errors.
But in the cases I do want to catch a specific error, the signature only tells me that a function returns an error, not which type. So I do feel that returning (int, error) is strictly worse than Java's checked exceptions if you care about errors.
This is elegantly solved with sum types. (And arguably needs sub typing.)
If instead of just returning (int, error) the function would return (int, parseError | outOfBoundsError) you would know that the parser function can fail on reading a number at all and on the number being to big/small to fit the type and handle then accordingly.
Saliently, Java in a sense has supported sum types in the throws declaration and the subsequent catch statements forever. Unfortunately it has not landed in other places where you can use types so you cannot use it for returning errors. Scala 3 supports this but has tiny adoption it seems.
It's helped, but that's runtime behaviour. The compiler can't help you with questions like "you didn't handle certain errors" or "the error you're trying to check can never occur".
Nor does that help with documentation, I'm guessing if I read a file then it might raise the error that the file does not exist. But what exactly is the technical type of that error? You have to search through the function's implementation, and functions that function calls, etc., to find out.
And it doesn't help with refactoring. If you create new.FileNotFoundError and change your function to return it, existing code which checks for old.FileNotFoundError will start to silently fail.
I never understood the convention of using single letter names for generic parameters. I guess this started in C++ and every language has copied that convention.
I think that code would be a lot easier to read if the types were called IN and OUT or In and Out or TIn and TOut or something like that.
Fairly sure it would predate even that, and go all the way to lambda calculus, and predicate logic before that, and that's where my knowledge stops and somebody else can tell us where the current conventions around variables in logic and mathematics come from.
I often use whole word for type annotation, when I can find meaningfull ones. I just type them in all caps to stay close to the convention.
I guess the single letter thing is laziness for a part. It's not simple to find words that represent the abstract idea behind the generic type without narrowing the possibilities. For array function, the Key Value from the sibling comment work but for more complex use case, it get complicated.
I'm not necessarily disagreeing, but I'm following the single-letter convention because "TIn" or "InputType" looks too much like an exported symbol. Whenever I tried to write type arguments like that, this threw me off, so I reverted to single letters.
It's a mix, because some stuff tends to just use `T`, but there's better descriptors elsewhere.
There's IList<T> but Task<TResult>
There's Action<T1, T2, T3, T4, T5, T6> but also Dictionary<TKey, TValue> and Map<TIn, TOut>
This stuff kind of "makes sense" once you're used to it, because it's difficult to say what IList<T> ought to have been called otherwise, IList<TContainee> is a mouthful, and Action<T1,...> simply suffers from the inability to specify an unknown number of generic parameters.
The article gives examples of situations where projects have failed, and states a solution.
> The [solution] is to organize the work into the smallest possible learnable chunks and continuously alternate between doing and learning.
It would be more convincing if the article gave examples of where that solution had been tried and succeeded. I mean this must have been tried somewhere, surely.
I feel this article is saying people from UK/US/etc. want to maximize upside, and people from DACH (Germany etc.) want to minimize downside.
I was a bit shocked when I talked to an Austrian colleague once and they told me they wanted to get into investing, but losing any money at any time was completely unacceptable. They had looked at investing in S&P 500 ETFs etc., but felt they must have misunderstood something, as they didn't understand why anyone would invest in anything that might go down, even temporarily.
So the thesis of the article definitely feels plausible to me.
If you invested in the S&P 500 in 1968, you'd be waiting until 1992 before you saw any return on that investment after accounting for inflation, and the inflation adjusted S&P 500 was on a net-losing streak for the whole period from 1968 to 1982 before it started going up again.
People believe the line will always go up, and maybe it will, but it's still at least possible for it to be on a quite painfully downward trend for over 12 years at a time.
I'd argue it's also not at all irrational to worry that we may be on the precipice of a similar situation right now, or perhaps even on the precipice of the situation at the end of the 1920s, where it would have taken more than 35 years for the S&P 500 to recover from its previous highs.
Your colleague is probably more risk-adverse than is rational (and I would say more risk-averse than most Austrians or Germans), but I would also argue that a lot of people blindly throwing all their retirement money at the S&P 500 might not realize just how much risk they are exposing themselves to.
Real estate, gold, crypto, in that order. That's where people from DACH invest, also valid for rest of the Europe. Mediterranean people additionally invest in lottery tickets. I wouldn't exaggerate saying people are keeping their gold bars in their otherwise empty investment apartments.
European banks play very negative role because they aggressively upsell their investment funds and schemes so for many people stock exchange means investment fund with management fee which oftentimes losses yet the growth, if happens, never catches up with the market growth.
> But if you have side effects and need something to happen exactly once it seems a lot more useful to communicate this, rather than pretending you did the thing.
I think it depends on whether the sender needs to know whether the thing was done during the request, or just needs to know that the thing was done at all. If the API is to make a purchase then maybe all the caller really needs to know is "the purchase has been done", no matter whether it was done this time or a previous time.
And in terms of a caller implementing retry logic, it's easier for the caller to just retry and accept the success response the second time (no matter if it was done the second time, or actually done the first time but the response got lost triggering the retry).
> Because jj's conflict resolution is fundamentally better
I don't know jj well so its merge algorithm may well be better in some aspects but it currently can't merge changes to a file in one branch with that file being renamed in another branch. Git can do that.
Apple refuses to remove things without clear replacements, for better or more often worse.
The 6k XDR has been replaced, apparently they've got something coming for the Mac Pro. I don't know why it wasn't the Mac Studio update last year. Maybe we find out at WWDC this year.
If you're taking a List[T] and all you want to do is to call list.size() then you don't care what type of list it is. In Java you can write a function which takes a List<?> but in Go you have to write List[T] so then the question becomes what is T? You have to make the function (or type you're a method on) generic. If you make the type generic then every user of your type also needs to specify T, etc.
I don't think it would be impossible to add that to Go. Allow List[?], which matches a List with any type parameter. Calling functions which don't involve the type parameter like list.size() would be fine, calling a method returning the type parameter like list.get(n) would return "any", and methods taking the type parameter like list.set(n, obj) would probably not be callable.