I think the problem with custom operators is they have custom associativity and precedence. Not saying this is a deal breaker, just the biggest issue you'll run into.
In toy examples, you can see all that because they'll show the declarations.
Here's a case[1] trying to explain monads by showing a simpler example:
parseP5_take2 s =
matchHeader (L8.pack "P5") s >>?
\s -> skipSpace ((), s) >>?
(getNat . snd) >>?
skipSpace >>?
\(width, s) -> getNat s >>?
skipSpace >>?
\(height, s) -> getNat s >>?
\(maxGrey, s) -> getBytes 1 s >>?
(getBytes (width * height) . snd) >>?
\(bitmap, s) -> Just (Greymap width height maxGrey bitmap, s)
Is all that left or right associative?
Generally, to read an expression when you have custom operators, you have to dig up the associativity and precedence. That's why I see many newer libraries avoiding custom operators, [2] vs [3].
I think you can do custom operators in a language, you just need to require that the associativity and precedence be declared in import. I'd also have named levels of associativity, so maybe:
import MyModule.Foo.Bar (op(!$!, additive, right), ...)
Now, that's some boilerplate in your imports, but for readability, it'd be huge. And automated tools can tweak stuff like this.
[1] http://book.realworldhaskell.org/read/monads.html
[2] http://hackage.haskell.org/package/pretty-1.1.3.6/docs/Text-...
[3] https://hackage.haskell.org/package/prettyprinter-1.2.1/docs...