Make rebindable fail work with overloaded strings

We propose that string literals generated by GHC and passed to user defined functions should have fromString applied. The only instance of such GHC generated strings we are aware of is via fail, so we propose to fix that case. Concretely, for monadic pattern match failures in the presence of the extensions RebindableSyntax and OverloadedStrings, we desugar to fail (fromString "error") instead of fail "error" .

Motivation

We might expect the following program to work:

{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE OverloadedStrings #-}

module Fail where

import Prelude hiding (fail)

foo x = do
    Just y <- x
    return y

newtype Text = Text String

fail :: Text -> a
fail (Text x) = error x

fromString :: String -> Text
fromString = Text

The intent is that the defined fail should take an argument of type Text which is obtained by implicit conversion from String.

However, it fails with,

Fail.hs:8:5-15: error:
* Couldn't match expected type `[Char]' with actual type `Text'
* In a stmt of a 'do' block: Just y <- x
  In the expression:
    do Just y <- x
       return y
  In an equation for `foo':
      foo x
        = do Just y <- x
             return y
  |
8 |     Just y <- x
  |     ^^^^^^^^^^^

Proposed Change Specification

We propose that monadic pattern match failure in the presence of RebindableSyntax and OverloadedStrings desugars to fail (fromString "error") instead of fail "error".

Effects and Interactions

With this modification, the example program from the “Motivation” section will type-check. It would be possible for a Prelude-replacement to eliminate String more completely, without having to retain it as the input type to fail.

We view this change primarily as a bug fix, which simplifies an unexpected corner. This proposal originally started out as https://gitlab.haskell.org/ghc/ghc/issues/15645.

Costs and Drawbacks

There may be a small number of libraries that fail to type-check, but any that have simply enabled the two extensions will continue to work, as fromString has an instance for String. In all likelihood, these are the libraries that have already tried to eliminate String, so are most likely to benefit from the change.

Alternatives

One can work around the problem by requiring the user to write fail = myFail . fromString, but that requires both fail and myFail to be in scope, and the name fail cannot be reused for the failure function in the monad typeclass.

Unresolved Questions

Should fromString be injected only when both OverloadedStrings and RebindableSyntax are enabled or just OverloadedStrings? We suggest only when both as, when RebindableSyntax is not enabled, the operation fail . fromString is equivalent to fail.

Implementation Plan

We (the proposal authors) will implement the change. Our plan is to modify getFailFunction and failFunction in RnExpr.hs so that if the RebindableSyntax and OverloadedStrings language extensions are enabled, then fail_op becomes fail . fromString rather than just fail.