Perhaps my comment went a bit off the rails. I think I’d summarize my comment as “I don’t think string interpolation should be special cased for string-like types”. But thinking about it more, perhaps I’m over-indexing on the cool stuff you can do with JavaScript template literals, and forgetting that f-strings in python and normal backticks in javascript are only strings, so maybe that is useful enough.
The flip side of this is that once the extension is in GHC, it’s hard to extend it without another extension.
FWIW most of these would be the same for all the different combinations. Lexing is the same for all the options, behavior when the extension is disabled is the same…
s is what Scala uses. I decided against f because if the extension is disabled, f"..." parses as a function call, and f is a really common name for a function, but s isnt. i could work, but i find it just as bespoke as s.
That’s because you want to reference functions from inside string literal, which I don’t think is necessary. As I wrote in #11, you can move the references out of the literal. It’s only slightly more bulky, but otherwise should be the same as string interpolation with a bunch of let-bindings outside.
Indeed, that’s why I’d like to draw a line between structured multiline strings (HTML/SQL), and short unstructured ones (logs, errors that kill the application). For the former keeping track of context is critical, so references should take up as little space as possible, i.e. you’d want to let-bind everything. For the latter the extra overhead of " <> foo _ <> " doesn’t really matter.
That’s not correct. PyF for example uses GHC API to parse antiquotes not haskell-src-meta.
And I think implementing string interpolation as a librrary first is very good idea. It allows to try out Iterpolate type class and to see how well pieces come together in practice. Hopefully it will show pain points. It’s way to check whether SQL interpolation could work or it runs into problems.
Just how bad error messages when string is polymorphic?
How painful is defining instances? Does it require to define Iterpolate Text, Interpolate String, Interpolate Lazy.Text, etc?
What about polymorphic data types? Would instances which call interpolate break for example SQL interpolation?
Even as a toy library it will give at least some experience on writing and using it and will allow people to try it out.
Another conspicuously missing thing is formatting options. Those are absolutely necessary for working with floating point. For example cos 1 = 0.5403023058681398. But most of the time one want control on how many digit should be shown. There’s also width specification for poor man’s ASCII tables
Regarding haskell-src-meta: There is ghc-hs-meta that does the same thing using the GHC parser, so it has a much lighter dependency footprint (as ghc is already pre-installed). It was initially extracted from PyF. I became a co-maintainer some time ago, but I only updated it for new GHC versions, didn’t have time to make bigger changes so far (ie ghc-hs-meta doesn’t yet cover the whole expression AST).
Yep, this seems like a big open question, particularly in the “multiple target types” version of the design. Many of these instances will be similar, and so there will be a gut instinct to define something like an overlappable (Show a, IsString s) => Interpolate a s instance, but that’s a can of worms (not least for the SQL or HTML use case, or any others where naive concatenation introduces security flaws).
You could probably work around this by providing a newtype for use with -XDerivingVia but that needs hammering out and testing.
There are many rough-cut libraries one could imagine to test aspects of this design:
Provide just the typeclasses and work with manually desugared expressions, to test type inference of desugared expressions and the developer experience providing types that work with the interpolation machinery;
Provide a TH quasiquoter that can test the syntactic expansion
Provide a GHC plugin that can test the syntactic expansion, by defining a function s :: Buildable s => String -> s with s = error "Did you forget to include the GHC plugin? Use the GHC plugin to find applications of s to string literals and replace them with the desugared expression.
Implementations of №2 and №3 can be made significantly simpler by allowing only variables in splices, and not arbitrary expressions. This is by far the most common use case, and will let users get a taste for the ergonomics.
I think interpolation is an easy feature to want in the abstract, but the devil is in the details. The MPTC design of class Interpolate will lead directly to an m×n instances problem if we’re not careful (m types to interpolate × n types to interpolate into). Such verbosity will make it less likely that library developers define all the necessary instance to make interpolation actually useful for application developers, severely limiting the value of shipping the extension at all.
@tomjaguarpaw is correct that the design space is vast. @brandonchinn178 mentions JS template literals, which makes me wonder if there’s a variant of JS-style tagged template literals that could work here. Make interpolation affect argument passing instead. An interpolation function could be a function of two arguments:
[Either String Int], a list of literal strings or indices into the argument set; and
HList xs, a heterogeneous list of the values that appear in splices.
Then you can return whatever type you want, choose to use Show or not, and each interpolator can do things its own way instead of having to design the core machinery for every potential use case.
-- I'm going to invent a really ugly syntax here, inspired by -XQuasiQuotes
-- Given:
message :: Text
message = <text|Hello ${name}. You are ${age} years old.>
name :: Text
name = "J. Random Hacker"
age :: Int
age = 42
-- Desugars to:
message = text
[ Left "Hello "
, Right 0
, Left ". You are "
, Right 1
, " years old."
]
(name :. age :. HNil)
I think the upside of getting interpolation right is large, but the downsides of getting it wrong (impractical levels of ambiguity, permits insecure string building, unclear which interpolation targets should be defined, confusing the typechecker, confusing newbies) are also large. Experimentation is actually pretty cheap here, especially for suggestion №1 and the restricted forms of №2 and №3, so I think it’s really worth doing that and not just saying “she’ll be right”.
I guess what I’m saying is: I personally think good interpolation will be good for the GHC dialect of the Haskell language, and its community. But I think we’re being asked to vote on alternatives too soon, without a chance to hold the tools in our hands.
I get the sense that there is a split between people who want as simple a design as possible and those who want as expressive design as possible. A good compromise might be to add two extensions:
StringInterpolation: just the minimal explicit design
OverloadedStringInterpolation: the most popular non-explicit design
I think that would hedge us well against the design risks. I think for either camp the worst case is that the “wrong” design choice is taken and then nobody from their camp will use this extension. By having two extensions, we can easily alleviate this.
Considering the size of the design space, personally I would be tempted to get the first one out quickly and spend some more time figuring out the exact details of the second.
Are these the HTML and SQL examples from your proposal? I wasn’t sure about these. Of course they are just sketches rather than full libraries, but I think this sort of thing has a fundamental flaw in that object language parsing errors must be delayed to runtime. I think that would make a string interpolation based library struggle to displace existing packages, and so they aren’t great for justifying the design. But if they are analogous to things that you can do in JS that would make more sense, since I think JS people care less about spotting errors at compile-time.
Aside: the HTML and SQL examples actually suggest a different design to me, namely one where foo"..." works like a quasiquoter so you have a function foo :: [Either String Exp] -> Q Exp in scope and that gets run at compile-time.
I like the idea of implementing this kind of like QualifiedDo - GHC would provide string interpolation syntax, desugaring, and semantics that are easy to understand using the StringInterpolation extension. The default semantics should have good type inference and present good error messages for beginning users. Then, I would want the ability provide custom semantics via something like QualifiedStringInterpretation to support additional expressivity - overloaded strings, SQL escaping, builders, whatever. You just need to import the definitions used in desugaring from a qualified module, and use the module name in the interpretation syntax - s"a ${x} b" becomes MyInterp.s"a ${x} b", and desugaring is identical except definitions from MyInterp are used instead of ones from Prelude.
Thank you for this work. I had not found how to vote on the survey, so here is my opinion with a bit of justification and discussion.
A1. B1. C1. As simple as possible.
I think that everything else can be added later using a source plugin (and GHC can even propose a new kind of plugin, “interpolation plugin” which would be called only on the interpolation string for efficiency):
Implicit interpolation can be added using a ghc plugin, just walk the interpolation chunks and wraps all the expression by the required interpolate calls.
Builder too, if users wants performance.
The different Interpolate class, or overlapping instances, … all of that can be experimented with a source plugin in external libraries and maybe in a few months we’ll have a reference plugin which is widely adopted and could be moved into main GHC.
All of that can be done without introducing interpolation in GHC syntax, it can be done right now as a source plugin (Either stealing the quasiquote syntax, or using multiline string from 9.12). I’ve tried to re implement PyF as a source plugin recently and except for performances (e.g. walking the HsExpr GhcPs ast to look for string to replace, hence why I suggest a more targeted “interpolation string plugin”), everything is really smooth: PyF as a source plugin by guibou · Pull Request #146 · guibou/PyF · GitHub.
So considering that everything can be refined later as external libraries (or refined directly in GHC), I’m in favor of doing the simplest solution right now. Adding features (such as the explicit interpolation) is always easy, removing features seems a bit more difficult.
Hi everyone, thanks a ton for the feedback. One thing I heard a lot of people mention is the desire for a prototype that people could build/run locally. So I implemented a prototype with 5 options discussed in this thread. See other post for details: GHC String Interpolation - Prototypes