Multiline template strings that don't break indentation

# Andy Earnshaw (3 years ago)

It's great to finally play around with template strings after noticing them in the latest Firefox nightly. However, I can't help but think we're repeating mistakes of the past when it comes to multiline strings and code tidiness.

Back when I had to write PHP code I used to feel sad that multiline strings had to look like this:

function my_function () {
    ... some levels of
        ... indentation
            $a = <<<EOD
This is a multiline string.
It makes my code look messy because I can't indent
subsequent lines without the white space appearing
in the resulting string. :-(
EOD;

           ... more code
    ...
}

I've always avoided the backslash newline escape in JavaScript for the same reason. As happy as I am to see multiline strings appear in JavaScript in the form of template strings, I'm not looking forward to seeing the untidiness that goes hand in hand with it. I'm wondering if there's a feasible solution that could allow us to keep our code indented across multiple lines without the formatting whitespace being included in the resulting string. Ideally, I'd like my multiline strings to look like this:

function myFunction () {
    var a = `This is a template string.
             Even though each line is indented to keep the
             code neat and tidy, the white space used to indent
             is not in the resulting string`;

    ...
}

As things stand, this wouldn't look great when output in a terminal window, a <textarea> or pre-formatted element in the browser. Also, minifiers

wouldn't know if it was safe to remove this white space when minifying and would likely leave it in. I realise I could write a tag function that deals with it, but that comes with additional runtime complexity and concatenation isn't really an option since I don't think this would make good code:

var a = tag`This is a template string.\n`
      + tag`Even though each line is indented to keep the\n`
      ...

I thought of a few potential solutions, but they come with problems of their own.

Solution 1: Ignore all space and tab characters following a newline until the first non-space or tab character. The problem with this is immediately obvious – what if someone wanted to output a string formatted with indentation at the start of a line? Well, there's always String.raw, which would return the string with the white space included, although I'll admit maybe it's not ideal.

Solution 2: Have the parser take note of the column where the delimiting ` character appears, and ignore white space on the next line only if the length of it equals this column number + 1. This would allow you to still write template strings like this:

var a = `This line is not indented.
             > But this line is.`

I've never written a parser, so I'm not sure how easy this would be to implement. I think it could potentially be confusing to people.

It could be that I'm the only person who is this fussy and it doesn't bother anyone else, but I hope that's not the case. I wonder if developers won't stick to the old methods of string concatenation and avoid template strings mostly – I've seen that happen a lot with PHP code. At least with here documents you could include white space as part of the delimiter and the content was consistently aligned (even if not indented), so it wasn't quite as bad as template strings.

Best

# Andrea Giammarchi (3 years ago)

would anything like this work already for you ?


    function myFunction () {
        var a = (`This is a template string.
                 Even though each line is indented to keep the
                 code neat and tidy, the white space used to indent
                 is not in the resulting string`
                ).replace(/^                 /gm, '');

        ...
    }

// or

    function myFunction () {
        var a = (`This is a template string.
                 Even though each line is indented to keep the
                 code neat and tidy, the white space used to indent
                 is not in the resulting string`).replace(
                 new RegExp('^' +
// how much to remove ?
'                ',
                 'gm'), '');

        ...
    }

# Boris Zbarsky (3 years ago)

On 9/9/14, 4:18 AM, Andy Earnshaw wrote:

It could be that I'm the only person who is this fussy and it doesn't bother anyone else, but I hope that's not the case.

We ran into this with Python multiline strings as well when we were working on Gecko's binding generator.

We (specifically Jason Orendorff) ended up implementing a "dedent" function which basically strips off the leading newline, if any, and then measures the number of spaces at the start of the string and removes that many spaces from each line. So you can do:

def foo(): return dedent( """ This is a string that will end up with no spaces at the beginnings of most of its lines. Except this one! """)

This worked great for our line-oriented (because they're C++ code or code templates) strings...

This addition definitely reduced the amount of concatenation we ended up doing.

# Allen Wirfs-Brock (3 years ago)

Well, just for fun

    const N = "\n";  //maybe we could find evocative unicode name.
    var a = `This is a template string. ${
           N}Even though each line is indented to keep the ${
           N}code neat and tidy, the white space used to indent ${
           N}is not in the resulting string`;
# Kevin Smith (3 years ago)

You can create a really flexible "dedent" function/tag pretty easily:

gist.github.com/zenparsing/5dffde82d9acef19e43c

Nice, right?

# Alexander Kit (3 years ago)

I would also hope that the trailing indentions will be cut off from the template string. We had the case in our template engine, multiline strings are supported and it behaives as follows:

  • first line: contains a non-whitespace character → take the complete string as is and exit, otherwise:
  • secondline - lastline → get the smallest indention
  • secondline - lastline → trim the indention
  • last line: contains only whitespaces → remove the line

This should cover all the cases, or do somebody has other ideas?

# Andy Earnshaw (3 years ago)

Allen's is pretty clever and I'd be almost tempted to use it ;-)

On Tue, Sep 9, 2014 at 4:44 PM, Kevin Smith <zenparsing at gmail.com> wrote:

You can create a really flexible "dedent" function/tag pretty easily:

gist.github.com/zenparsing/5dffde82d9acef19e43c

Nice, right?

I wrote a function-as-a-tag that "dedented" (since that's the word we're all using) when I was playing around too. It's really nice.

On Tue, Sep 9, 2014 at 5:26 PM, Alexander Kit <alex.kit at atmajs.com> wrote:

I would also hope that the trailing indentions will be cut off from the

template string. We had the case in our template engine, multiline strings are supported and it behaives as follows:

  • first line: contains a non-whitespace character → take the complete string as is and exit, otherwise:
  • secondline - lastline → get the smallest indention
  • secondline - lastline → trim the indention
  • last line: contains only whitespaces → remove the line

This should cover all the cases, or do somebody has other ideas?

Git and linting tools usually bark at you if you have trailing white space anyway, if you don't have your editor automatically removing it. That aside, are you suggesting that a template string like this:

var a = `
        This has
        no indentation
        `;

should have the indentation removed, but not in the case where a non-white space character is between the first and the newline? Wouldn't that be a tad confusing? Also, it's common in console applications to have a trailing newline (although you could just concat one of course). Taking the smallest amount of indentation away from each line is interesting, though, and probably less confusing than my suggestion of removing white space up to the column of the first.

# Sebastian Zartner (3 years ago)

On 9 September 2014 16:51, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Well, just for fun

    const N = "\n";  //maybe we could find evocative unicode name.
    var a = `This is a template string. ${
           N}Even though each line is indented to keep the ${
           N}code neat and tidy, the white space used to indent ${
           N}is not in the resulting string`;

To me this looks still somewhat hacky. Couldn't a simple keyword be added at the the end of a template string? So it would be something like this:

    var a = `This is a template string.
             Even though each line is indented to keep the
             code neat and tidy, the white space used to indent
             is not in the resulting string` keepindentation;

This would explicitly keep the spaces at the beginning of each line while without the keyword they would be removed. Of course the keyword may also indicate the opposite depending on parsing would be chosen as default. Surely the name for this keyword is not optimal yet and would need to be discussed.

Sebastian

# 李白|字一日 (3 years ago)

I would prefer

var a = This is a template string. Even though each line is indented to keep the code neat and tidy, the white space used to indent is not in the resulting string keepindentation`;

as a multiple line string,

2014-09-11 13:55 GMT+08:00 Sebastian Zartner <sebastianzartner at gmail.com>:

# Salvador de la Puente González (3 years ago)

Notice we are dealing with literals and I would want to keep the strings as literal as possible so I think this is a syntax issue as the programmatic solution like regexp substitution is always available.

My proposal: var s = This is a multiline string. It keeps literal unless ' finding that mark. It ' delimit the beginning ' of the text and must ' be the first one. I mean ' this other ' is interpreted ' as a quote. The double ' quote is valid as well.;

This is syntax can be automatized by text editors. To avoid ambiguity, the following is a syntax error:

var s = This is a syntax error to have characters before ' but only for the first;

To not find a quote mark in a line implies to take the full line literally.

The syntax is quite informative as it points the specific column where literal start again.

Notice the case you specifically want to write the delimiter is quite rare and you always can provide:

var s = Take this \' literal;

You don't need the quotes to be even aligned.

WDYT?

# Brendan Eich (3 years ago)

The tag goes at the front. What's missing from the design that can't be provided as a standard exported deindent function?

# Allen Wirfs-Brock (3 years ago)

On Sep 11, 2014, at 1:05 AM, Brendan Eich wrote:

The tag goes at the front. What's missing from the design that can't be provided as a standard exported deindent function?

exactly:

    var a = dontIndent
            `This is a template string.
             Even though each line is indented to keep the
             code neat and tidy, the white space used to indent
             is not in the resulting string`;

or, following the precedent to 'raw':

    var a = String.noIndentation
            `This is a template string.
             Even though each line is indented to keep the
             code neat and tidy, the white space used to indent
             is not in the resulting string`;
# Sebastian Zartner (3 years ago)

The tag goes at the front.

I know. I didn't see this functionality as a tag, though, but rather as a

flag for the client. Having this functionality available as a tag has some consequences. See below.

What's missing from the design that can't be provided as a standard exported deindent function?

Concatenating tags, maybe? And the function itself?

exactly:

    var a = dontIndent
            `This is a template string.
             Even though each line is indented to keep the
             code neat and tidy, the white space used to indent
             is not in the resulting string`;

or, following the precedent to 'raw':

    var a = String.noIndentation
            `This is a template string.
             Even though each line is indented to keep the
             code neat and tidy, the white space used to indent
             is not in the resulting string`;

And if someone wants to use his own tag? Would he have to reimplement dontIndent or String.noIndentation by himself?

What I meant above with tag concatenation would be something like this:

    var a = String.noIndentation myTag
            `This is a template string.
             Even though each line is indented to keep the
             code neat and tidy, the white space used to indent
             is not in the resulting string`;

So first the template string would be parsed by String.noIndentation and subsequently by myTag.

Sebastian

# Salvador de la Puente González (3 years ago)

On 12 Sep 2014 07:40, "Sebastian Zartner" <sebastianzartner at gmail.com>

And if someone wants to use his own tag? Would he have to reimplement

dontIndent or String.noIndentation by himself?

What I meant above with tag concatenation would be something like this:

AFAIK a couple of brackets around the second tag and the string do the trick.

var a = tag1 (tag2 Template);

Though tag1 tag2 would be optimal.

    var a = String.noIndentation myTag
            `This is a template string.
             Even though each line is indented to keep the
             code neat and tidy, the white space used to indent
             is not in the resulting string`;

So first the template string would be parsed by String.noIndentation and

subsequently by myTag.

# Marius Gundersen (3 years ago)

On Fri, Sep 12, 2014 at 7:40 AM, Sebastian Zartner < sebastianzartner at gmail.com> wrote:

And if someone wants to use his own tag? Would he have to reimplement dontIndent or String.noIndentation by himself?

What I meant above with tag concatenation would be something like this:

    var a = String.noIndentation myTag
            `This is a template string.
             Even though each line is indented to keep the
             code neat and tidy, the white space used to indent
             is not in the resulting string`;

So first the template string would be parsed by String.noIndentation and subsequently by myTag.

String.noIndent could return an array similar to what a tag function expects to get as its arguments, where the first item is the string split into chunks and the rest of the array is the values to be inserted. That would let you do:

var query = sql(...String.noIndent`
    select * from myDB
    where username='${user}'
    and password='${password}'
`);

Marius Gundersen

# Kevin Smith (3 years ago)

And if someone wants to use his own tag? Would he have to reimplement dontIndent or String.noIndentation by himself?

I implemented exactly what you want (modulo de-indent specifics) here:

gist.github.com/zenparsing/5dffde82d9acef19e43c

This should be left in userland for now, I think.

# Alexander Kit (3 years ago)

On 10 September 2014 01:01, Andy Earnshaw <andyearnshaw at gmail.com> wrote:

Git and linting tools usually bark at you if you have trailing white space anyway

Template string is a literal, so any of the linter should not complain about the trailing whitespace inside, if so this should considered as a bug in linter itself.

*> should have the indentation removed, but not in the case where a

non-white space character is between the first ` and the newline? *

Correct, with the new line developer explicitly defines he wants his template string to be auto deindented, otherwise leaves the string as is. It is more flexible, and there is no need in any of the tags as was suggested.

# Brendan Eich (3 years ago)

Kevin Smith wrote:

This should be left in userland for now, I think.

Absolutely. The trick with design, to paraphrase N. Wirth, is leaving things out.

With the right primitives (tag in front is function call short hand, you can compose function calls as Salvador showed), the usability is fine and the core language avoids creature feep.

# C. Scott Ananian (3 years ago)

On Fri, Sep 12, 2014 at 12:35 PM, Brendan Eich <brendan at mozilla.org> wrote:

With the right primitives (tag in front is function call short hand, you can compose function calls as Salvador showed), the usability is fine and the core language avoids creature feep.

Agreed, although I would like to see something very similar to Kevin's implementation added to the standard library (String.dedent?), so that a million authors don't reinvent the dedent function in multiple slightly-different ways.

# Kevin Smith (3 years ago)

Agreed, although I would like to see something very similar to Kevin's implementation added to the standard library (String.dedent?), so that a million authors don't reinvent the dedent function in multiple slightly-different ways.

Yes - it sounds like it might be a good cowpath to pave once the cows have been set loose.

# Owen Densmore (3 years ago)

I'm always surprised we keep forgetting coffeescrip, coffeescript.org/t:

 Multiline strings are allowed in CoffeeScript.
 Lines are joined by a single space unless they end with a backslash.
 Indentation is ignored.

mobyDick = "Call me Ishmael. Some years ago -- never mind how long precisely -- having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world..."

and:

 Block strings can be used to hold formatted or indentation-sensitive

text (or, if you just don't feel like escaping quotes and apostrophes). The indentation level that begins the block is maintained throughout, so you can keep it all aligned with the body of your code.

html = """ <strong> cup of coffeescript </strong> """

# Merih (3 years ago)

This might be beyond the current state of template strings but wouldn't it be nice if there was a delimiter character we can use to depict the beginning of each line of a multiline string? A similar solution like Scala multliline strings but without stripMargin method at the end.

For example:

var multiLineString = `This is a template string.
                                  |Even though each line is indented to

keep the |code neat and tidy, the white space used to indent |is not in the resulting string`

And if we actually want to include vertical bar we can escape it?

-- Merih Akar

# Domenic Denicola (3 years ago)

ALL of these things can be accomplished with your own custom tag. I really encourage you and others to learn about the "tag" part of "tagged template string".

# Alexander Kit (3 years ago)

On 19 September 2014 01:52, Domenic Denicola <domenic at domenicdenicola.com>

wrote:

ALL of these things can be accomplished with your own custom tag.

Yes, it can be accomplished with the custom tag function. But this is not the argument, as also a lot of other things from ES6 spec can be accomplished with some custom libraries (see all the shims), but we anyway have them in es6. And so, I would also like to see here the spec. regarding the indentation. (prefer native to custom)

# Brendan Eich (3 years ago)

Alexander Kit wrote:

On 19 September 2014 01:52, Domenic Denicola <domenic at domenicdenicola.com <mailto:domenic at domenicdenicola.com>> wrote:

ALL of these things can be accomplished with your own custom tag. 

Yes, it can be accomplished with the custom tag function. But this is not the argument, as also a lot of other things from ES6 spec can be accomplished with some custom libraries (see all the shims), but we anyway have them in es6. And so, I would also like to see here the spec. regarding the indentation. (/prefer native to custom)/

"Custom" means prototype it first, as ksmith said: "in userland". Do that, even now using Traceur, and then let's talk about a built-in.