That's an interesting point. From a strictly theoretical perspective you're right. I think it's that generics are more abstract and have a higher blast radius. Eg, you add an argument to a method, you need to update usage of that method. You add a generic to a class, you need to update everywhere that class is used.
The fact that arguments are less abstract also I believe tends to prevent them from bubbling all the way to the top. Generics can often only be populated at the top-level usage. Function arguments I find don't usually bubble up that far.
> you add an argument to a method, you need to update usage of that method. You add a generic to a class, you need to update everywhere that class is used.
You're talking about two different things here, though. What if you add a generic parameter to a function? It'll often be inferred at existing call sites, but worst case, same as any other change to a function's signature.
The reason I'm relating them is that they're both changes to a class, that have different blast radii and different levels of abstraction.
In the worst case, adding a nongeneric parameter to a function could have the same impact. I've never heard of that happening though. I'm trying to express how I've observed things working in practice, not the theoretical boundaries of what could happen here.
So lets say you add a concrete parameter to a method. You update the usages. Somewhere the output gets stored in an existing class. So you add a new field to this class of the correct type. You're done.
Let's say you do the same with a generic. Now when you add that field to that class, it also has to be generic over that type. Now you need to update all the places where that class was used.
If your code is overly generic throughout, the likelihood of this having secondary or tertiary effects and having a runaway refactor becomes pretty darn high.
Being too abstract will always get you in trouble, and it'll probably look pretty similar. I'm just saying it's very easy to do with generics and harder to do with less abstract techniques.
> In the worst case, adding a nongeneric parameter to a function could have the same impact. I've never heard of that happening though.
Can you clarify this? I'm reading it as "I've never heard of anyone adding a parameter to a function" and that's so far from my experience that I'm either misreading or you work in a vastly different field than I do.
> Now when you add that field to that class, it also has to be generic over that type. Now you need to update all the places where that class was used.
Only if you need that to be generic too. If you change an int to a T, and you want to preserve the existing behavior for existing callers, they just call it as f<int>() instead of f(). Languages with good type inference will do that for you without changing the calling code as written.
Apologies, I mean that, if you came up with some kind of pathologically bad architecture, it could encounter the same failure mode when trying to add a parameter to a function (like, the callers need to add a parameter, and their callers, etc). But as you note, adding parameters to a function is routine, and I've never heard of this happening. I've definitely added parameters in a way that was tiresome and required me to go higher up the chain of callers than I would have liked, but not in a way that spun out of control.
I'm not really sure what to say at this point really. I think we're miscommunicating somehow. Would you agree that if we are too abstract with our architecture, we'll end up with a brittle and difficult to maintain architecture?
I do agree with that, and with the implication that one shouldn't add generics (or other abstraction) where they don't provide enough value for their costs.
But I'm confused because your example (adding a generic parameter to a function) seems to be an example of adding abstraction to code that did not previously have enough abstraction.
The fact that arguments are less abstract also I believe tends to prevent them from bubbling all the way to the top. Generics can often only be populated at the top-level usage. Function arguments I find don't usually bubble up that far.