I was tempted by this argument but I think I reject it. String interpolation is also going be one of the first things a newbie hits, and I worry that a newbie will try to use this feature to write an apparently-simple program, mistype something, and get a big error about missing instances involving builders and probably MPTCs. At which point, our hypothetical new Haskeller will frisbee the laptop out the window in frustration — a great loss.
A design that provides clean code for newbies but incomprehensible errors when you do the slightest thing wrong is a massive double-edged sword. (I base this on personal experience with advanced “if it typechecks, it definitely works”-type libraries that use advanced GHC features, where I failed to produce a working program or resigned myself to doing things in very bad ways to get anything at all.
Whatever we design here and bake into GHC needs to fail gracefully in a newbie’s hands. IMHO, this includes at least the following situations:
- Syntactic errors: missing
}
, writing#foo
instead of#{foo}
(or whatever splice character we chose), writing#{foo (}
or other parse errors in the splice; - Interpolating values of types with missing instances;
- Interpolating where the output is an
IsString t => t
(i.e., underconstrained for some reason) - Interpolating a value of a type where there are instances
Interpolate Foo String
but notInterpolate Foo Text
(or vice-versa, or involving lazy/strictText
, or…)
That makes me lean towards simplicity at the expense of expressive power and being the complete string-templating solution for all cases.
This makes me think that interpolating only to String
might even be the best option? This removes the MPTC and then you can define a single-parameter class Interpolate
. Then defining an interaction with -XOverloadedStrings
that inserts fromString
into the results of an interpolated overloaded string, ideally in a way that avoids materialising the entire string as individual Char
along the way.
Desugaring example:
{-# LANGUAGE OverloadedStrings #-}
t :: Text
t = s"Name: ${name}, Age: ${show age}"
-- Could desugar to this. Unsure if it's better to have all the string chunks converted to `Text` ASAP, or whether it's best to consume the entire string and allocate a single `Text`.
t = mconcat
[ fromString "Name: "
, fromString name
, fromString ", Age: "
, fromString (show age)
]