PIN's are 4 digits in the UK. Hashing them (even with a per user salt) would only produce 10k possible hashes. To have anything that was difficult to brute force would also just not practical on CPU constrained mobile devices.
When traditional banks do "1st, 2nd and 4th character" checks (i.e. via their apps), some go to pretty good lengths to protect these, even given the limited entropy.
These systems are typically backed by HSMs, and the HSM generates a high-entropy "challenge". The challenge is sent to the client, which combines the challenge with the characters the user entered. The resulting hash is sent back to the HSM, which can verify internally that the correct characters were selected, by computing the expected hash for the correct response (hopefully in constant-time).
It seems there is a bigger issue here -- Monzo used card PINs as a generic security credential in their app, and was storing them in a recoverable form (albeit only accessible to a very small number of staff). That's not standard practice elsewhere that I'm aware of - given use of the PIN is the "proof" you approved the transaction, it's usually used only in environments with dedicated PIN entry devices (ATM, card readers), rather than commodity devices (phones), unless those go through PCI-style verification and approval.
That's why you don't hash it, you encrypt it. What they want to do here can be solved using something called channel binding.
The problem is not that we can't do this securely, it's two separate but related problems:
1. The web stack was never designed for RPC and attempts to use it that way are often unsafe.
2. Poor or missing libraries to make common patterns safe. Why was the logging framework happy to log the PIN, well, because the type system didn't know the PIN is sensitive. Why was the PIN even sent in the clear at all, well, because libraries to do this securely aren't well known and the web doesn't help.