Feature request: String interpolation

I’m not opposed to string interpolation syntax, but it’s a surprisingly complicated feature:

  • We have almost no new syntax to give it aside from an escape code in “normal” string literals. Backticks are not an option since we use that for infix expressions. I’m going to assume \{ ... } escape syntax (or something equivalent like \${ ... } or whatever sigil you choose).
  • The issue always comes down to how you lex and parse strings then.
  • String interpolation must emit a series of delimiter tokens. It’s no longer a single token, but you must have things like TokStringStart, TokStringMid, TokStringEnd to represent the different boundaries because they can contain arbitrary expressions.
  • We already use }, ], and ) for other delimiters so this will require a stateful, context-sensitive lexer. It must know that it has emitted string “start” or “mid” token in order to decide how it should lex the delimiter.
  • Parsing literals is no longer a matter of casing on a single token, but now must consume an unbounded number of tokens.

All of these are surmountable of course, but are also very non-trivial.

I’m going to throw out an alternative using typeclases (and instance chains!):

class Interp a where
  interp :: String -> a

instance interpString :: Interp String where
  interp a = a
else instance interpFunction :: Interp a => Interp (String -> a) where
  interp a b = interp (a <> b)
else instance interpShow :: (Show b, Interp a) => Interp (b -> a) where
  interp a b = interp (a <> show b)

i = interp

test = i "foo" 42 "bar" true "baz"

There are a few things to note about this:

  • It requires no language changes.
  • You can write any interpolation function you want this way (for example, trimming and separating by spaces).
  • You can add directives in the middle of interpolation (it can be an extensible DSL).
  • It’s the same number characters typed as “normal” interpolation
  • With a very simple inliner it will compile to the code you’d write by hand.
14 Likes