Quadrature encoders work because they are actually two encoders in a single package, offset by some portion. It’s a 2-bit sensor, so it takes two digital inputs to work.
The reason for it being two encoders is so you can infer direction. A single encoder, a 1-bit sensor, can only send 1’s and 0’s: 1010101010101010. Every transition from 0 to 1 (or 1 to 0, depending) is a single encoder tick. Unfortunately, this looks the same whether the encoder is going forwards or backwards.
By adding a second bit, the values allowed go from 0 and 1 to 00, 01, 10, and 11 (or 0, 1, 2, 3). As the encoder spins in one direction, the readout looks something like this: 013201320132. Going the other way, it looks more like this: 0231023102310231. Going one way then reversing gives a clear indication: 1023201. This can’t be expressed with less than two bits of input information.
As for ultrasonic, one port is for sound emission and the other is for sound reception, as there are two components to an ultrasonic sensor: a speaker and a microphone. This is also something that can’t really be done with less than two ports.
I’m not sure about the physical feasibility of this idea, but it would definitely be prohibited by <R21>:
However, if you want to conserve 3-wire ports, and you don’t care about directional info (i.e., you only care about the speed of your encoder and not about its position), it may be possible to connect only one of the ports, and write some software to get speed data from that single encoder.