The concept of escaping is perfectly fine; the demented part is having it turn strings into strings, which allows it to be applied any number of times (including zero).
That can easily be avoided by producing a different type of thing to the input, e.g.
escapeHTML : String -> HTMLChunk
escapeSQL : String -> SQL
escapeShell : String -> Shell
etc.
and having our "execute" API requires that thing as input, e.g.
returnResponse : HTML -> ()
runQuery : SQL -> ResultSet
runCmd : Shell -> (ExitCode, Stdout, Stderr)
This way, we're forced to escape (to get a valid argument for the API), and we can't double-escape (since escaping functions require a string as input)