You generally shouldn't need to "pull IO through half this module", since you can just lift the large pure part with "fmap" to operate on the inner IO. But using these combinators effectively does take some learning.
The approach to IO that Haskell uses is not "academic", it is extremely practical: By typing effects you get immense practical, not academic, benefits.
Pulling and pushing bytes outside the system is trivial.
Though doing so with any performance, might not be.