As I was typing the file handle example, I realised that it may be a bit annoying to design a do-notation desugaring which would both handle IO (a .<= c) and IO a. Maybe we should just bake in the constraint in IO directly.
Something like:
data IO c a = RealWorld ->. (RealWorld, a .<= c)
(plus some complication so that a can be either linear or unrestricted)
We can always use IO () a for the standard case.