syntax...(strings/interpolation/here docs)

Wed, 13 Feb 2002 14:36:01 +0000

> > Does anybody with their elbows in the
> > code think variable interpolation and/or
> > multi-line strings are good/doable ideas? 
> > Would this be the sort of change one could make
> > without a lot of previous familiarity with
> > the implementation of Hugs/Ghc?

Unlike my rough proposal, one should aim for a combination of
(mostly) in-Haskell implementation and (some) pre-processing.  As
Thomas Nordin has pointed out to me in private email, Hugs (Dec
2001) already supports this (module lib/hugs/Quote.hs and flag +H).

The real trick is to have the same support in all implementations..

> I don't think it is really necessary to add the feature to the language,
> since you can program something very similar for yourself in user-code.

It is not really necessary, but "very similar" isn't good enough
for the purpose (see further down).

- haven't tried with other systems, but Hugs at least has some limit
  on maximum token length (4000). This is a lot easier to avoid if
  string variable interpolation implicitly breaks up tokens.

- I've tried to work with Haskell's \\-multiline strings - I don't
  find them useable. The extra characters at the end and start of
  lines make them less readable and less writeable than necessary
  for this kind of applications (my current workaround is to use
  break strings on lines, either with explicit concatenation or
  with lists of strings and the good old unlines to get rid of those

- as said above, I do agree that there should be no complex language
  extensions for what can be done in Haskell, and Hugs' combination
  of Quote module and +H support gets close to that. If you have to
  change string quoting (to preserve formatting), you might as well
  throw in variable interpolation (only needs "..$(var).." ->
  ".."++quote var++"..", the rest is Haskell code). It is similar to
  what I posted, but the ``$(var) $$''-syntax is simpler, there are no
  overlapping instances, and there is an explicit function to trim
  leading whitespace instead of the extra layout rule I assumed.

Here's your example, with Hugs' Quote:

{- :set +H -}
import Quote

hereDocument v w =
  ``Multi-line string starts here
    and uses string gap delimiters.
    Variable names like $(a) are filled in from the
    bindings specified in the `with` clause,
    e.g. $(a) = $$a, $(b) = $$b
    and unused bindings pass through e.g. $(c) = $$c.''
    (a,b,c) = (v,w,"$c")

After all, the purpose of here-documents is readability in programs
that have to generate programs or formatted text (e.g., the popular
Haskell/CGI libraries, or libraries generating XML/HTML/VRML/SVG/..).
In those contexts, they are an important matter of convenience - you 
just write the text template you want to generate, filled in with 
variables. Meta-programming is difficult enough without asking for
trouble (extra \ everywhere, explicit \n, by hand conversion from
any type a to String, no checks of variable bindings instead of
lexical scoping).

The only disadvantage I've seen so far (apart from that maximum
token length..) is the need to disambiguate numeric types for the 
overloaded quote, but that's a standard Haskell problem.


> Here's a sample of a single-character macro expansion within strings,
> that took a couple of minutes to write.
>     module Printf where
>     import Maybe(fromMaybe)
>     hereDocument :: (Show a, Show b) => a -> b -> String
>     hereDocument v w =
>         "Multi-line string starts here\
>     \    and uses string gap delimiters.\n\
>     \    Variable names like $$a are filled in from the\n\
>     \    bindings specified in the `with` clause,\n\
>     \    e.g. $$a = $a, $$b = $b\n\
>     \    and unused bindings pass through e.g. $$c = $c."
>                `with` [('a',show v), ('b',show w)]
>     with :: String -> [(Char,String)] -> String
>     [] `with` env           = []
>     ('$':'$':cs) `with` env = '$': cs `with` env
>     ('$':c:cs)   `with` env = s ++ cs `with` env
>                               where s = fromMaybe ('$':c:[]) (lookup c env)
>     (c:cs)       `with` env =  c : cs `with` env