How about we talk about the examples in the article. Stream and StreamReader? How should that be handled by making interfaces? You can extend those types but you can't apply new interfaces to the existing types.
If you want to write a function that takes immutable collections and does not accept mutable ones, that's generally impossible to do in a language with only structural typing. In a language like C# with nominal typing, you can have that function accept an interface that only immutable collection types implement, such as IImmutableList.
> How about we talk about the examples in the article. Stream and StreamReader?
I have no idea what they do or what they're meant for. It's certainly possible to define bad interfaces, I don't think anyone's denying that.
> You can extend those types but you can't apply new interfaces to the existing types.
Sure, that's a limitation and there are various ways to navigate that tradeoff (e.g. adapters, or a Haskell/Rust-style trait system where you can apply interfaces to existing types but you have to do so explicitly). My point is that structural typing is not a flawless approach that solves all your problems.
Actually I think the point is kind of moot since the article claims these have identical Read() methods but that is not the case. One returns bytes and the other returns chars and so they have different signatures.