Let me put things into context because there is a lot of confusion with Tokens, and some technical issues are often lexical issues. We unfortunately use token for two different things: a resource and its manager.
Or basically, a token is not really an resource/item you can own, store and exchange freely, in a peer-to-peer way. Instead, it is a type of distributed application, or dapp, that maintains a register of who has how much of a certain resource. The fact that it is a dapp has two important consequences: it is autonomous and non-modifiable. Therefore its behavior at conception is final and all changes to the register is also final.
With that said, when they speak of 'sending 100 ERC20', what is really meant is telling the dapp to lower the balance of the sender and raise that of the recipient on their register, regardless of the recipient. If the recipient is another dapp which has no method to communicate with the ERC20 Token, then it won't be aware about the resource you made available to it in the first place, even less how to use it. But the resource is still present. It is not lost, just wasted (hence the monkey analogy).
Now the approve() method is indeed one way to circumvent the issue, but it suffers from major design flaws:
it transforms a 1-step, 1 party process to a 2-step, 2-party process. In other words, it is akin to dumping the credit card to go back to checkbooks, with all the known complications it entails.
it is up to the sender, or their software client/wallet to use approve(), which few will do by lack of awareness. If it's not mandatory and makes things more complex, it won't be adopted.
Limiting the possible addresses in the Token registry to non-contract addresses is one way to solve the problem.
I hope that helps.