I'm sorry, I used an overly understated word. I meant they are inappropriate analogies – I don't think they demonstrate the point you are looking to make.
These keywords are there to mark the potential impact to control flow of the function being called – either by potentially exiting in the case of try
, or suspending execution and allowing re-entry in the case of await
. They appear at each point because that is important information at each point (though there is a compromise that this is only needed once on a single line when multiple places in a statement might need marking). The repetition does not have some educative benefit.
It is true that sometimes they mark points where this impact to control flow cannot matter. I think it would be very interesting to explore being able to elide then in single-statement function bodies, for example (a discussion for another thread :). But in the general case, they are needed every time because it is impractical to only require them when they matter.
This is very different from this discussion, where some
does not have an equivalent situation – some
is not marking "beware, your choice here has significant consequences". By taking a generic argument, you are not closing off any important avenues of expressivity for either the caller or the implementer. Whereas by taking any
, you are.
The arguments being made here are mainly that requiring some
teaches the user of the distinction. That is not what try
and await
are for, and neither should it be what some
is for.
Joe discusses this when he talks about the limitations of value-level abstraction in the Improving the UI of generics post.
Receiving or returning a generic argument preserves type information. If you take some Collection
, you have access to the types representing things like the Index
and Iterator
that any Collection
cannot provide. Over time, we may thin away at this advantage through more opening even in open-coded use, but no amount of compiler cleverness will eliminate the difference completely.
Taking a generic argument is near-indistinguishable* to the caller from taking an existential argument. But within the implementation of the function, it provides many benefits. This is why, for function arguments at the very least, it's the clear favorite to be the default, and not just a peer.
The benefits are more mixed for function return types and storage declarations. Still, I think the benefits of some
being the default, and any
being the one that needs explicit marking. My hope would be we agree at least for function arguments, and then move on to talk about those other cases.
* I know you can write code where the distinction is observable; but those examples are not important enough to be material to the discussion IMO.