[Review] SE-0168: Multi-Line String Literals

Finally.. a new Xcode toolchain <http://johnholdsworth.com/swift-LOCAL-2017-04-12-a-osx.tar.gz&gt; is available largely in sync with the proposal as is.
(You need to restart Xcode after selecting the toolchain to restart SourceKit)

I personally am undecided whether to remove the first line if it is empty. The new
rules are more consistent but somehow less practical. A blank initial line is almost
never what a user would want and I would tend towards removing it automatically.
This is almost what a user would it expect it to do.

IMO the rule should be simple: """ is a *marker* of the begin/end of multi-line string, not the part of multi-line string itself.
And so there is no question regarding "new line" symbol injection after leading """ and before/after trailing """. Each line *between* leading and trailing triple quotes - the only lines of multi-string.

And as soon as last line *of text* inside multi-line string has new line, the result text also should have it. It is common to have, for example, a text file where last line ends with \n symbol, i.e. with "new line" character. It is common to end the text in text editor by pressing Enter, so last symbol will be \n. Even in XML/JSON file.
Why we invent a complex/confused rules for multi-line string in Swift code?

I.e.

this:
"""
one
two
"""
should be just the same as "one\ntwo\n"

If one wants some advanced tuning of line ends, he/she can use a backslash, for example
"""
one
two\
"""
should produce "one\ntwo"

Btw, in which situations it is important to remove the trailing \n character from *multi-line* string? How is common this use-case? Because I can't think of XML/JSON/SQL text, they should not depend on trailing new line symbol.

···

On 12.04.2017 22:07, John Holdsworth via swift-evolution wrote:

I’m less sure the same applies to the trailing newline. If this is a syntax for
multi-line strings, I'd argue that they should normally be complete lines -
particularly since the final newline can so easily be escaped.

letlongstring = """\
             Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \
             tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \
             quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\
             """

print( """\
             Usage: myapp <options>

             Run myapp to do mything

             Options:
             -myoption - an option
             """ )

(An explicit “\n" in the string should never be stripped btw)

Can we have a straw poll for the three alternatives:

1) Proposal as it stands - no magic removal of leading/training blank lines.
2) Removal of a leading blank line when indent stripping is being applied.
3) Removal of leading blank line and trailing newline when indent stripping is being applied.

My vote is for the pragmatic path: 2)

(The main intent of this revision was actually removing the link between how the
string started and whether indent stripping was applied which was unnecessary.)

On 12 Apr 2017, at 17:48, Xiaodi Wu via swift-evolution <swift-evolution@swift.org >> <mailto:swift-evolution@swift.org>> wrote:

Agree. I prefer the new rules over the old, but considering common use cases, stripping the leading and trailing newline makes for a more pleasant experience than not stripping either of them.

I think that is generally worth prioritizing over a simpler algorithm or even accommodating more styles. Moreover, a user who wants a trailing or leading newline merely types an extra one if there is newline stripping, so no use cases are made difficult, only a very common one is made more ergonomic.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

If I understand #2 correctly than it work for this literal.

let x = """↵
abc↵
"""
Iff we only remove the top new line when the below part is indented than the literal from above would produce "\nabc\n", which I wouldn’t expect.

Compared to:

let x = """↵
··abc↵
··"""
In the multi-lined version of that literal, the starting delimiter does only one job: “look I’m gonna provide a multi-line string in between me and the closing delimiter”. That’s it’s only job. (Not yet officially proposed, nor it’s in the current toolchain.)

The closing delimiter however covers the leading precision and the indent of the current literal. However this is partly visible for a literal that fully fits onto your screen. (That’s already included in current toolchain.)

The trailing precision is covered by the backslash (because it’s visible and intuitive for the developer), otherwise all whitespace characters are stripped at the end and a new line character is implicitly added to that line. (Not handled in the current toolchain.)

···

--
Adrian Zubarev
Sent with Airmail

Am 12. April 2017 um 21:08:19, John Holdsworth via swift-evolution (swift-evolution@swift.org) schrieb:

Finally.. a new Xcode toolchain is available largely in sync with the proposal as is.
(You need to restart Xcode after selecting the toolchain to restart SourceKit)

I personally am undecided whether to remove the first line if it is empty. The new
rules are more consistent but somehow less practical. A blank initial line is almost
never what a user would want and I would tend towards removing it automatically.
This is almost what a user would it expect it to do.

I’m less sure the same applies to the trailing newline. If this is a syntax for
multi-line strings, I'd argue that they should normally be complete lines -
particularly since the final newline can so easily be escaped.

    let longstring = &quot;&quot;&quot;\\
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \\
        tempor incididunt ut labore et dolore magna aliqua\. Ut enim ad minim veniam, \\
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat\.\\
        &quot;&quot;&quot;

    print\( &quot;&quot;&quot;\\
        Usage: myapp &lt;options&gt;
        
        Run myapp to do mything
        
        Options:
        \-myoption \- an option
        &quot;&quot;&quot; \)

(An explicit “\n" in the string should never be stripped btw)

Can we have a straw poll for the three alternatives:

1) Proposal as it stands - no magic removal of leading/training blank lines.
2) Removal of a leading blank line when indent stripping is being applied.
3) Removal of leading blank line and trailing newline when indent stripping is being applied.

My vote is for the pragmatic path: 2)

(The main intent of this revision was actually removing the link between how the
string started and whether indent stripping was applied which was unnecessary.)

On 12 Apr 2017, at 17:48, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

Agree. I prefer the new rules over the old, but considering common use cases, stripping the leading and trailing newline makes for a more pleasant experience than not stripping either of them.

I think that is generally worth prioritizing over a simpler algorithm or even accommodating more styles. Moreover, a user who wants a trailing or leading newline merely types an extra one if there is newline stripping, so no use cases are made difficult, only a very common one is made more ergonomic.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

The example you have provided with where you return a string happens to be short before the multi-line string starts and therefore it looks ‘okay’ at that moment. However this is the exact example which creates the most problems with the multi-line string literal model.

If we’re going to allow that form, that it would be natural to think one could also write like this:

"""
Foo
Bar"""
But this is not correct. Furthermore the form from your example also raises the question what happens if you add a newline after the starting delimiter? Does it add a new line to the string, because at any other line it would. This creates an inconsistent behavior and overcomplicates everything?!

The starting delimiter should really only tell you, that the next line will be the start of your string and the indent is controlled by the closing delimiter. This is way easier. Seriously you will only need to sacrifice one line for that behavior, but the ident of your string would align perfectly.

case .isExprSameType(let from, let to):
return """
    checking a value with optional type \(from) against dynamic type \(to) \
    succeeds whenever the value is non-'nil'; did you mean to use '!= nil'?\
    """
This eliminates all the previously mentioned issues.

I strongly discourage the idea of allowing trailing whitespaces in a line which has no backslash at the end. Normal string indicates all its whitespaces visually, even if it’s hard to count them, but it does this job for you. At least you could approximately guess how many trailing whitespaces you’d have or count them with the cursor if needed.

The following string could have 200 characters, but you wouldn’t even notice if I don’t tell you so:

"""
foo
"""
That is a really bad idea to support this. Your IDE maybe strip the whitespaces, so even if you’d want them to be there, you’ll tap into a corner where you’ll have to sacrifice that IDE feature for the whole project in order to support it for multi-line string literals in Swift. That is simply wrong.

I called from the beginning for trailing precision, and that’s exactly what the backslashes are meant for, plus that they disable the new line injection.

"""
foo \
"""
Can you now guess how many characters this string will have? Now it’s way easier to tell.

I’m not a compiler expert, but shouldn’t the tokenizer simply swallow every space characters after a backslash until it finds a new line character at the end to the current line? That’s what I would expect it to do, because otherwise you’ll end up with error messages, because you cannot rely on your IDE in that case. Notice that this is different from the trailing stripping behavior I was talking above.

···

--
Adrian Zubarev
Sent with Airmail

Am 13. April 2017 um 00:20:41, Brent Royal-Gordon (brent@architechies.com) schrieb:

Wow, maybe I shouldn't have slept.

Okay, let's deal with trailing newline first. I'm *very* confident that trailing newlines should be kept by default. This opinion comes from lots of practical experience with multiline string features in other languages. In practice, if you're generating files in a line-oriented way, you're usually generating them a line at a time. It's pretty rare that you want to generate half a line and then add more to it in another statement; it's more likely you'll interpolate the data. I'm not saying it doesn't happen, of course, but it happens a lot less often than you would think just sitting by the fire, drinking whiskey and musing over strings.

I know that, if you're pushing for this feature, it's not satisfying to have the answer be "trust me, it's not what you want". But trust me, it's not what you want.

Moving to the other end, I think we could do a leading newline strip *if* we're willing to create multiline and non-multiline modes—that is, newlines are _not allowed at all_ unless the opening delimiter ends its line and the closing delimiter starts its line (modulo indentation). But I'm reluctant to do that because, well, it's weird and complicated. I also get the feeling that, if there's a single-line mode and a multi-line mode, we ought to treat them as truly orthogonal features and allow `"`-delimited strings to use multi-line mode, but I'm really not convinced that's a good idea.

(Note, by the way, that heredocs—a *really* common multiline string design—always strip the leading newline but not the trailing one.)

Adrian cited this example, where I agree that you really don't want the string to be on the same line as the leading delimiter:

let myReallyLongXMLConstantName = """<?xml version="1.0"?>
<catalog>
<book id="bk101" empty="">
<author>John Doe</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
</book>
</catalog>\
"""

But there are lots of places where it works fine. Is there a good reason to force an additional newline in this?

case .isExprSameType(let from, let to):
return """checking a value with optional type \(from) against dynamic type \(to) \
succeeds whenever the value is non-'nil'; did you mean to use '!= nil'?\
"""

I mean, we certainly could, but I'm not convinced we should. At least, not yet.

In any case, trailing newline definitely stays. Leading newline, I'm still thinking about.

As for other things:

* I see zero reason to fiddle with trailing whitespace. If it's there, it might be significant or it might not be. If we strip it by default and we shouldn't, the developer has no way to protect it. Let's trust the developer. (And their tooling—Xcode, I believe Git, and most linters already have trailing whitespace features. We don't need them too.)

* Somebody asked about `"""`-delimited heredocs. I think it's a pretty syntax, but it's not compatible with single-line use of `"""`, and I think that's probably more important. We can always add heredocs in another way if we decide we want them. (I think `#to(END)` is another very Swifty syntax we could use for heredocs--less lightweight, but it gives us a Google-able keyword.)

* Literal spaces and tabs cannot be backslashed. This is really important because, if you see a backslash after the last visible character in a line, you can't tell just by looking whether the next character is a space, tab, or newline. So the solution is, if it's not a newline, it's not valid at all.

I'll respond to Jarod separately.

On Apr 12, 2017, at 12:07 PM, John Holdsworth <mac@johnholdsworth.com> wrote:

Finally.. a new Xcode toolchain is available largely in sync with the proposal as is.
(You need to restart Xcode after selecting the toolchain to restart SourceKit)

I personally am undecided whether to remove the first line if it is empty. The new
rules are more consistent but somehow less practical. A blank initial line is almost
never what a user would want and I would tend towards removing it automatically.
This is almost what a user would it expect it to do.

I’m less sure the same applies to the trailing newline. If this is a syntax for
multi-line strings, I'd argue that they should normally be complete lines -
particularly since the final newline can so easily be escaped.

    let longstring = &quot;&quot;&quot;\\
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \\
        tempor incididunt ut labore et dolore magna aliqua\. Ut enim ad minim veniam, \\
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat\.\\
        &quot;&quot;&quot;

    print\( &quot;&quot;&quot;\\
        Usage: myapp &lt;options&gt;
        
        Run myapp to do mything
        
        Options:
        \-myoption \- an option
        &quot;&quot;&quot; \)

(An explicit “\n" in the string should never be stripped btw)

Can we have a straw poll for the three alternatives:

1) Proposal as it stands - no magic removal of leading/training blank lines.
2) Removal of a leading blank line when indent stripping is being applied.
3) Removal of leading blank line and trailing newline when indent stripping is being applied.

My vote is for the pragmatic path: 2)

(The main intent of this revision was actually removing the link between how the
string started and whether indent stripping was applied which was unnecessary.)

On 12 Apr 2017, at 17:48, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

Agree. I prefer the new rules over the old, but considering common use cases, stripping the leading and trailing newline makes for a more pleasant experience than not stripping either of them.

I think that is generally worth prioritizing over a simpler algorithm or even accommodating more styles. Moreover, a user who wants a trailing or leading newline merely types an extra one if there is newline stripping, so no use cases are made difficult, only a very common one is made more ergonomic.

--
Brent Royal-Gordon
Architechies

You say "this is the example set by `print`", but I don't think anything else actually *follows* that example. No other I/O operation in Swift behaves this way. The underlying `TextOutputStream.write(_:)` doesn't; the I/O in Foundation doesn't; file descriptor I/O doesn't. Concatenation certainly doesn't; nor does anything else you might do to assemble several multiline string literals into a whole. So I think `print()` is the exception here, not the rule.

In my opinion, modulo the "newline after leading delimiter" question, if code like this example:

  var xml = """
      <?xml version="1.0"?>
      <catalog>
      """
  
  for (id, author, title, genre, price) in bookTuples {
      xml += """
              <book id="bk\(id)">
                  <author>\(author)</author>
                  <title>\(title)</title>
                  <genre>\(genre)</genre>
                  <price>\(price)</price>
              </book>
          """
  }
  
  xml += """
      </catalog>
      """

Doesn't assemble the kind of string that it's blatantly obvious the user is trying to assemble, I think the syntax has failed.

`print()` is the outlier. It's an important enough outlier that we should probably help the user notice when they're about to get it wrong, but an outlier it is.

···

On Apr 12, 2017, at 5:39 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Wed, Apr 12, 2017 at 5:20 PM, Brent Royal-Gordon <brent@architechies.com <mailto:brent@architechies.com>> wrote:
Wow, maybe I shouldn't have slept.

Okay, let's deal with trailing newline first. I'm *very* confident that trailing newlines should be kept by default. This opinion comes from lots of practical experience with multiline string features in other languages. In practice, if you're generating files in a line-oriented way, you're usually generating them a line at a time. It's pretty rare that you want to generate half a line and then add more to it in another statement; it's more likely you'll interpolate the data. I'm not saying it doesn't happen, of course, but it happens a lot less often than you would think just sitting by the fire, drinking whiskey and musing over strings.

I know that, if you're pushing for this feature, it's not satisfying to have the answer be "trust me, it's not what you want". But trust me, it's not what you want.

This is not a very good argument. If you are generating files in a line-oriented way, it is the function _emitting_ the string that handles the line-orientedness, not the string itself. That is the example set by `print()`:

print("Hello, world!") // Emits "Hello, world!\n"

--
Brent Royal-Gordon
Architechies

Now that I had some sleep I actually revise my opinion about the last line. I took a few hours to sum my thoughts together in a gist, here is a formatted version of it: simple-multi-line-string-literal-model.md · GitHub

Simple ‘multi-line string literal’ model

Core features:

Omitting of (most) backslashes for ".
Altering the string with implicit new line injection at the end of the line.
Consequences of #1:

To omit escaping the quote character, the delimiter characters for the multi-line string literal will be tripled quotes """, also similar to other programming languages.

When a standard string literal contains at least 5 quotes, then the usage of a multi-line string literal will be shorter.

"<a href=\"\(url)\" id=\"link\(i)\" class=\"link\">" // With escapes
"""<a href="\(url)" id="link\(i)" class="link">""" // With tripled literals
Consequences of #2:

To fully support this feature, we need to compromise the design for simplicity and intuitivity.

This feature needs precision for leading and trailing spaces.
Alternatively one would need a way to disable new line injection to also support code formatting.
Two ways of writing a multi-line string literal:

Single line version """abc""" is trivial and already was shown above.

The multi-line version comes with a few compromises for simplicity of rules:

""" // DS (delimiter start)
foo // s0
foo // s1
foo // s2
""" // DE (delimiter end)
The string content is always written between the lines DS and DE (delimiter lines).

To not to go the continuation quotes path, the left (or leading) precision is handled by the closing delimiter (1. compromise). The closing delimiter is also responsible for the indent algorithm, which will calculate the stripping prefix in line DE and apply stripping to lines s0 to sn.

Right (or trailing) precision of each line from s0 to sn (notice n equals 2 in the example above) is handled by a backslash character (2. compromise).

The right precision comes at a price of losing the implicit new line injection, however this was also a requested feature (3. compromise). That means that the backslash serves two jobs simultaneously.

New line injection happens only on lines s0 to s(n - 1) (4. and last compromise of the design). The last line sn (or s2 above) does not inject a new line into the final string. This implies that in this line a backslash character handles only right precision, or one could say it’s reduced to one functionality.

Important:

Because whitespace is important to these examples, it is explicitly indicated: · is a space, ⇥ is a tab, and ↵ is a newline.

Leading/trailing precision and indent (1. and 2. compromise):

// Nothing to strip in this example (no ident).
let str_1 = """↵
foo↵
"""

// No right precision (no backslash) -> whitespaces will be stripped.
let str_2 = """↵
foo··↵
"""

// Same as `str_2`
let str_3 = """↵
foo····↵
"""

// Line `DE` of the closing delimiter calculates the indent prefix
// `··` and strips it from `s0` (left precision).
let str_4 = """↵
··foo↵
··"""

// Line `DE` of the closing delimiter calculates the indent prefix
// `····` and strips it from `s0` (left precision).
// No right precision (no backslash) -> whitespaces will be stripped.
let str_5 = """↵
····foo··↵
····"""

// Line `DE` of the closing delimiter calculates the indent prefix
// `⇥ ⇥ ` and strips it from `s0` (left precision).
// Right precision is applied (backslash). In this case the literal
// contains only a single line of content, which happens to be
// also the last line before `DE` -> backslash only serves precision.
let str_6 = """↵
⇥ ⇥ foo\↵
⇥ ⇥ """

// Line `DE` of the closing delimiter calculates the indent prefix
// `·⇥ ·⇥ ` and strips it from `s0` (left precision).
// No right precision (no backslash) -> whitespaces will be stripped.
let str_7 = """↵
·⇥ ·⇥ foo··↵
·⇥ ·⇥ """

let string_1 = "foo"

str_1 == string_1 // => true
str_2 == string_1 // => true
str_3 == string_1 // => true
str_4 == string_1 // => true
str_5 == string_1 // => true
str_6 == string_1 // => true
str_7 == string_1 // => true
A false multi-line string literal, which compiles but emits a warning and proves a fix-it:

let str_8 = """↵
··foo↵
····"""

str_8 == string_1 // => true
warning: missing indentation in multi-line string literal
  ··foo!
    ^
  Fix-it: Insert "··"
The stripping algorithm calculates the prefix indent from the closing delimiter line DE and tries to strip it in lines s0 to sn if possible, otherwise each line, which could not be handled correctly will emit an individual warning and a fix-it.

The stripping algorithm removes every whitespace on the end of each line from s0 to sn iff there is no right precision, annotated through a backslash like ··foo··\↵. This behavior is essential and aligns well with the precision behavior of a standard string literal " ", otherwise a multi-line string literal like

"""
foo
"""
can contain 3 characters or 10 characters or even 1000 characters, but the developer couldn’t tell or even approximately guess.

The correct way of fixing this, as already mentioned above, is by striping all white spaces after the last non-space character of the line, unless the right precision is explicitly annotated with a backslash.

"""
foo \
"""
Disabling new line injection (3. compromise):

The examples we’ve used so far had only a single content line, so we couldn’t showcase the behavior yet. New lines are only injected into a multi-line string if it has at least two content lines.

let str_9 = """↵
····foo↵
····bar↵
····"""

let str_10 = """↵
····foo↵
····bar↵
····baz↵
····"""

let string_2 = "foo\nbar"
let string_3 = "foor\nbar\nbaz"

str_9 == string_2 // => true
str_10 == string_3 // => true
To disable new line injection one would need to use the backslash for right precision.

let str_11 = """↵
····foo\↵
····bar↵
····"""

let str_12 = """↵
····foo\↵
····bar\↵
····baz↵
····"""

str_11 == string_2 // => false
str_12 == string_3 // => false

str_11 == "foorbar" // => true
str_12 == "foobarbaz" // => true
Remember that the last content line sn does not automatically inject a new line into the final string!

New line injection except for the last line (4. compromise):

The standard string literal like "foo" only contains its string content from the starting delimiter to the closing delimiter. The discussion on the mailing list suggests that the multi-line string literal should also go that route and not inject a new line for the last content line sn. str_9 is a good example for that behavior.

Now if one would want a new line at the end of the string, there are a few options to achieve this:

// Natural way:
let str_13 = """↵
····foo↵
····bar↵
····↵
····"""

// Remember the last content line does not inject a `\n` character by default
// so there is no need for `\n\` here (but it's possible as well)!
let str_14 = """↵
····foo↵
····bar\n↵
····"""

str_13 == "foo\nbar\n" // => true
At first glance the behavior in str_13 seems odd and inconsistent, however it actually mimics perfectly the natural way of writing text paragraphs.

[here is a blank line]↵
text text text tex text↵
text text text tex text↵
[here is a blank line]
This is easily expressed with the literal model expressed above:

let myParagraph = """↵
····↵
····text text text tex text↵
····text text text tex text↵
····↵
····"""

···

--
Adrian Zubarev
Sent with Airmail

Am 13. April 2017 um 02:39:51, Xiaodi Wu (xiaodi.wu@gmail.com) schrieb:

On Wed, Apr 12, 2017 at 5:20 PM, Brent Royal-Gordon <brent@architechies.com> wrote:
Wow, maybe I shouldn't have slept.

Okay, let's deal with trailing newline first. I'm *very* confident that trailing newlines should be kept by default. This opinion comes from lots of practical experience with multiline string features in other languages. In practice, if you're generating files in a line-oriented way, you're usually generating them a line at a time. It's pretty rare that you want to generate half a line and then add more to it in another statement; it's more likely you'll interpolate the data. I'm not saying it doesn't happen, of course, but it happens a lot less often than you would think just sitting by the fire, drinking whiskey and musing over strings.

I know that, if you're pushing for this feature, it's not satisfying to have the answer be "trust me, it's not what you want". But trust me, it's not what you want.

This is not a very good argument. If you are generating files in a line-oriented way, it is the function _emitting_ the string that handles the line-orientedness, not the string itself. That is the example set by `print()`:

print("Hello, world!") // Emits "Hello, world!\n"

Once upon a time, if I recall, this function was called `println`, but it was renamed. This particular example demonstrates why keeping trailing newlines by default is misguided:

print(
  """
  Hello, world!
  """
)

Under your proposed rules, this emits "Hello, world!\n\n". It is almost certainly not what you want. Instead, it is a misguided attempt by the designers of multiline string syntax to do the job that the designers of `print()` have already accounted for.

If we were to buy your line of reasoning and adapt it for single-line strings, we would arrive at a rather absurd result. If you're emitting multiple single-line strings, you almost certainly want a space to separate them. Again this is exemplified by the behavior of `print()`:

print("Hello", "Brent!")

This emits "Hello Brent!" (and not "HelloBrent!"). But we do not take that reasoning and demand that "This is my string" end with an default trailing space, nor do we have `+` concatenate strings by default with a separating space.

Moving to the other end, I think we could do a leading newline strip *if* we're willing to create multiline and non-multiline modes—that is, newlines are _not allowed at all_ unless the opening delimiter ends its line and the closing delimiter starts its line (modulo indentation). But I'm reluctant to do that because, well, it's weird and complicated. I also get the feeling that, if there's a single-line mode and a multi-line mode, we ought to treat them as truly orthogonal features and allow `"`-delimited strings to use multi-line mode, but I'm really not convinced that's a good idea.

(Note, by the way, that heredocs—a *really* common multiline string design—always strip the leading newline but not the trailing one.)

Adrian cited this example, where I agree that you really don't want the string to be on the same line as the leading delimiter:

let myReallyLongXMLConstantName = """<?xml version="1.0"?>
<catalog>
<book id="bk101" empty="">
<author>John Doe</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
</book>
</catalog>\
"""

But there are lots of places where it works fine. Is there a good reason to force an additional newline in this?

case .isExprSameType(let from, let to):
return """checking a value with optional type \(from) against dynamic type \(to) \
succeeds whenever the value is non-'nil'; did you mean to use '!= nil'?\
"""

I mean, we certainly could, but I'm not convinced we should. At least, not yet.

In any case, trailing newline definitely stays. Leading newline, I'm still thinking about.

As for other things:

* I see zero reason to fiddle with trailing whitespace. If it's there, it might be significant or it might not be. If we strip it by default and we shouldn't, the developer has no way to protect it. Let's trust the developer. (And their tooling—Xcode, I believe Git, and most linters already have trailing whitespace features. We don't need them too.)

* Somebody asked about `"""`-delimited heredocs. I think it's a pretty syntax, but it's not compatible with single-line use of `"""`, and I think that's probably more important. We can always add heredocs in another way if we decide we want them. (I think `#to(END)` is another very Swifty syntax we could use for heredocs--less lightweight, but it gives us a Google-able keyword.)

* Literal spaces and tabs cannot be backslashed. This is really important because, if you see a backslash after the last visible character in a line, you can't tell just by looking whether the next character is a space, tab, or newline. So the solution is, if it's not a newline, it's not valid at all.

I'll respond to Jarod separately.

On Apr 12, 2017, at 12:07 PM, John Holdsworth <mac@johnholdsworth.com> wrote:

Finally.. a new Xcode toolchain is available largely in sync with the proposal as is.
(You need to restart Xcode after selecting the toolchain to restart SourceKit)

I personally am undecided whether to remove the first line if it is empty. The new
rules are more consistent but somehow less practical. A blank initial line is almost
never what a user would want and I would tend towards removing it automatically.
This is almost what a user would it expect it to do.

I’m less sure the same applies to the trailing newline. If this is a syntax for
multi-line strings, I'd argue that they should normally be complete lines -
particularly since the final newline can so easily be escaped.

    let longstring = &quot;&quot;&quot;\\
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \\
        tempor incididunt ut labore et dolore magna aliqua\. Ut enim ad minim veniam, \\
        quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat\.\\
        &quot;&quot;&quot;

    print\( &quot;&quot;&quot;\\
        Usage: myapp &lt;options&gt;
        
        Run myapp to do mything
        
        Options:
        \-myoption \- an option
        &quot;&quot;&quot; \)

(An explicit “\n" in the string should never be stripped btw)

Can we have a straw poll for the three alternatives:

1) Proposal as it stands - no magic removal of leading/training blank lines.
2) Removal of a leading blank line when indent stripping is being applied.
3) Removal of leading blank line and trailing newline when indent stripping is being applied.

My vote is for the pragmatic path: 2)

(The main intent of this revision was actually removing the link between how the
string started and whether indent stripping was applied which was unnecessary.)

On 12 Apr 2017, at 17:48, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

Agree. I prefer the new rules over the old, but considering common use cases, stripping the leading and trailing newline makes for a more pleasant experience than not stripping either of them.

I think that is generally worth prioritizing over a simpler algorithm or even accommodating more styles. Moreover, a user who wants a trailing or leading newline merely types an extra one if there is newline stripping, so no use cases are made difficult, only a very common one is made more ergonomic.

--
Brent Royal-Gordon
Architechies

Wow, maybe I shouldn't have slept.

:-)

Okay, let's deal with trailing newline first. I'm *very* confident that trailing newlines should be kept by default. This opinion comes from lots of practical experience with multiline string features in other languages. In practice, if you're generating files in a line-oriented way, you're usually generating them a line at a time. It's pretty rare that you want to generate half a line and then add more to it in another statement; it's more likely you'll interpolate the data. I'm not saying it doesn't happen, of course, but it happens a lot less often than you would think just sitting by the fire, drinking whiskey and musing over strings.

I know that, if you're pushing for this feature, it's not satisfying to have the answer be "trust me, it's not what you want". But trust me, it's not what you want.

Moving to the other end, I think we could do a leading newline strip *if* we're willing to create multiline and non-multiline modes—that is, newlines are _not allowed at all_ unless the opening delimiter ends its line and the closing delimiter starts its line (modulo indentation). But I'm reluctant to do that because, well, it's weird and complicated. I also get the feeling that, if there's a single-line mode and a multi-line mode, we ought to treat them as truly orthogonal features and allow `"`-delimited strings to use multi-line mode, but I'm really not convinced that's a good idea.

(Note, by the way, that heredocs—a *really* common multiline string design—always strip the leading newline but not the trailing one.)

Adrian cited this example, where I agree that you really don't want the string to be on the same line as the leading delimiter:

  let myReallyLongXMLConstantName = """<?xml version="1.0"?>
                                       <catalog>
                                          <book id="bk101" empty="">
                                             <author>John Doe</author>
                                             <title>XML Developer's Guide</title>
                                             <genre>Computer</genre>
                                             <price>44.95</price>
                                          </book>
                                       </catalog>\
                                       """

But there are lots of places where it works fine. Is there a good reason to force an additional newline in this?

      case .isExprSameType(let from, let to):
        return """checking a value with optional type \(from) against dynamic type \(to) \
                succeeds whenever the value is non-'nil'; did you mean to use '!= nil'?\
                “""

Yes, I think there is a good reason to force an additional newline in this: the beginnings of the line are not aligned otherwise or the editor would be required to create custom indentation based on the position of the leading "““ and whether it is followed by text or not (shudder).

To summarize:

1.
I support Adrian’s idea of requiring that the opening triple quote may not be followed by text if and only if the closing triple quote is not preceded by text, i.e. only the following two forms are allowed:

a. single line form
"""text"“"

b. multiline form (whitespace allowed before each line, of course)
"“"↵
text↵
""“

2.
Furthermore I would strip the first newline and keep the last newline. As Adrian put it quite well: each line between the triple quotes is part of the string and ends with a newline (except if ending with a backslash). This is an easy mental model and aligns with the most common use cases.

"“"↵
text↵
""“
would therefore be equal to """text↵"“"

3.
In addition I support Adrian’s idea of stripping trailing whitespace from the content (except newlines). To keep trailing whitespace it must be ended with a backslash and thereby made visible. That is similar to my idea of always using \n for the newlines within a multiline string which has already been incorporated into the proposal (thanks for that!).

-Thorsten

···

Am 13.04.2017 um 00:20 schrieb Brent Royal-Gordon <brent@architechies.com>:

I mean, we certainly could, but I'm not convinced we should. At least, not yet.

In any case, trailing newline definitely stays. Leading newline, I'm still thinking about.

As for other things:

* I see zero reason to fiddle with trailing whitespace. If it's there, it might be significant or it might not be. If we strip it by default and we shouldn't, the developer has no way to protect it. Let's trust the developer. (And their tooling—Xcode, I believe Git, and most linters already have trailing whitespace features. We don't need them too.)

* Somebody asked about `"""`-delimited heredocs. I think it's a pretty syntax, but it's not compatible with single-line use of `"""`, and I think that's probably more important. We can always add heredocs in another way if we decide we want them. (I think `#to(END)` is another very Swifty syntax we could use for heredocs--less lightweight, but it gives us a Google-able keyword.)

* Literal spaces and tabs cannot be backslashed. This is really important because, if you see a backslash after the last visible character in a line, you can't tell just by looking whether the next character is a space, tab, or newline. So the solution is, if it's not a newline, it's not valid at all.

I'll respond to Jarod separately.

On Apr 12, 2017, at 12:07 PM, John Holdsworth <mac@johnholdsworth.com <mailto:mac@johnholdsworth.com>> wrote:

Finally.. a new Xcode toolchain <http://johnholdsworth.com/swift-LOCAL-2017-04-12-a-osx.tar.gz&gt; is available largely in sync with the proposal as is.
(You need to restart Xcode after selecting the toolchain to restart SourceKit)

I personally am undecided whether to remove the first line if it is empty. The new
rules are more consistent but somehow less practical. A blank initial line is almost
never what a user would want and I would tend towards removing it automatically.
This is almost what a user would it expect it to do.

I’m less sure the same applies to the trailing newline. If this is a syntax for
multi-line strings, I'd argue that they should normally be complete lines -
particularly since the final newline can so easily be escaped.

        let longstring = """\
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \
            tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \
            quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\
            """

        print( """\
            Usage: myapp <options>
            
            Run myapp to do mything
            
            Options:
            -myoption - an option
            """ )

(An explicit “\n" in the string should never be stripped btw)

Can we have a straw poll for the three alternatives:

1) Proposal as it stands - no magic removal of leading/training blank lines.
2) Removal of a leading blank line when indent stripping is being applied.
3) Removal of leading blank line and trailing newline when indent stripping is being applied.

My vote is for the pragmatic path: 2)

(The main intent of this revision was actually removing the link between how the
string started and whether indent stripping was applied which was unnecessary.)

On 12 Apr 2017, at 17:48, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Agree. I prefer the new rules over the old, but considering common use cases, stripping the leading and trailing newline makes for a more pleasant experience than not stripping either of them.

I think that is generally worth prioritizing over a simpler algorithm or even accommodating more styles. Moreover, a user who wants a trailing or leading newline merely types an extra one if there is newline stripping, so no use cases are made difficult, only a very common one is made more ergonomic.

--
Brent Royal-Gordon
Architechies

Was the / intended or did you meant to write \?

Personally I don’t think we’d need that behavior and can fallback to the natural way using a blank lines or explicit way using the \n character to solve that issue at the top or bottom of the multi-line string. The model I’m pushing already would support “paste-in block of text as-is”. :)

Glad to have you joining the discussion.

···

--
Adrian Zubarev
Sent with Airmail

Am 13. April 2017 um 19:33:42, David Sweeris via swift-evolution (swift-evolution@swift.org) schrieb:

On Apr 12, 2017, at 07:52, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

Am 12.04.2017 um 15:40 schrieb Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org>:

Hey folks,

We've revised the proposal again. The main difference: You no longer need an initial newline to enable indentation stripping, and stripping no longer removes that newline even if it is present. (Adrian Zubarev and I believe some others argued for this.) We

Hmm, not sure if I like these changes. I expect that almost all strings won't begin with a newline and a majority won’t end with a newline. The new design would require a leading backslash almost all the time and a trailing backslash often, which is ugly:

let mystring = "““\
text text
text text\
"““

(I think I've read the whole thread, but it can be easy to miss parts on the phone. I think this is different than what's already been discussed... sorry if this has already been suggested and I just missed it.)

What having this input:
let mystring = "““
text text
text text
"““
yielding this output:
text text
text text

And this input:
let mystring = "““/
text text
text text
/"““
yielding this output:

text text
text text

(Sorry about not putting in all the symbols... I don't know a way to type them on my phone)

If you think of the "/" as more of a "toggle whether the next character is escaped" instead of specifically "escape the next character", it makes sense (kinda)... or the outputs could be reversed. Either way, it allows easy access to both options and preserves the "paste-in block of text as-is" goal.

- Dave Sweeris
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

It is complex, and yet Rails has done this kind of thing pretty successfully (in a different, dynamic-language-y way) for about eight years: <https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/string/output_safety.rb&gt;

You just have to scale back your ambitions slightly. Your goal is not to somehow, through the type system, prevent the construction of an invalid string. Your goal is to ensure that interpolated content is, by default, escaped enough to prevent injection attacks. Developers can still make escaping mistakes, but those mistakes will err on the side of escaping too much instead of too little. You can't prevent failure entirely, but you can fail secure instead of failing insecure.

···

On Apr 9, 2017, at 8:46 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

For XML, I know that you have this XMLString idea, but I think that it would be very complex to implement in practice. XML has several different contexts in which escaping has to be different. For instance, you shouldn't escape the same things in an attribute value as in a comment, or in an XML text node, or in a CDATA node, and that means that you have to be aware of what you're looking for at the point where interpolation happens. It's also possible to come up with uncheckable/incorrect cases (like `<foo \(bar)>`), meaning that it either has to accept anything in some cases or be failable (and besides, "just remove :XMLString and it works!").

--
Brent Royal-Gordon
Architechies

Sorry, I realized I had a little more to say about this.

···

On Apr 9, 2017, at 8:46 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

C# was able to eliminate the whole class of SQL injections by introducing a convenient and powerful syntax for queries that is not string-based. I think that Swift should be moving in that direction, especially since no one has made any other case for long strings so far.

LINQ is, by all accounts, a pretty nice feature. It also seems to have taken at least five years to develop. We cannot realistically hope to put that kind of effort into a beautifully crafted, language-integrated solution to every problem that a Swift developer could solve with a five-line string literal, particularly when many will be highly domain-specific: generating config files or source code, communicating with a half-dozen NoSQL databases with disparate query languages and capabilities, even just writing tests that check the output of systems designed to do these things. And even if you somehow designed elaborate features to handle all of those, or were willing to insist that developers should design elaborate subsystems to handle them, there's still the cases where you're just flat-out generating large chunks of text to show to human beings.

Multiline string literals are a basic tool with vast and diverse uses. Even if some individual use case might be better addressed by a bespoke feature, it is well worth it to build a tool so simple, versatile, and easy to add.

--
Brent Royal-Gordon
Architechies

Wow, thanks a lot for this detailed rewrite! Looks like something worth evaluating now :-)

Is the “magic" leading whitespace removal a good idea to support indentation.

It's worth mentioning that the updated proposal contains ways to avoid the automatic indentation processing.

Gwendal

···

Le 11 avr. 2017 à 15:35, John Holdsworth via swift-evolution <swift-evolution@swift.org> a écrit :

I feel discussion on this proposal is nearing general agreement that while the gist of it
it seems reasonable to many there is also not enough detail in it as submitted.

Brent has person-fully stepped in and rewritten the draft to translate it into a detailed
specification which I’ve updated here:

https://github.com/johnno1962a/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md

Perhaps we could use the remaining time on the review to pre-evaluate this draft should
we get the chance to resubmit with modifications to maximise its chances. Key questions:

1) Does Swift need multi-line string literals?
2 ) Is “””long strings””” the way to go subject to a discussion about the precise delimiter
3) Is the “magic" leading whitespace removal a good idea to support indentation.

The main change on the revised proposal from the the original submitted is Thorsten’s
suggestion that Windows line endings in a source file be normalised to \n in the literal.

John

On 10 Apr 2017, at 12:09, Thorsten Seitz <tseitz42@icloud.com <mailto:tseitz42@icloud.com>> wrote:

Am 09.04.2017 um 18:29 schrieb John Holdsworth <mac@johnholdsworth.com <mailto:mac@johnholdsworth.com>>:

Hi, John here, the submitter of the proposal.

First up, I must apologise for putting Brent on the spot when I resubmitted this altered proposal from last year. That was my mistake.

Second up, apologies if the proposal is rather vague on details. In some sense this was intentional as I didn’t want to get too bogged down in specifics (and not at all to do with my limitations as a technical writer!)

I guess we need to build up consensus more slowly by asking the following questions separately so it can be resubmitted rather than giving a binary +/-1 on the proposal as it stands.

1) Does Swift need multi-line string literals?

Yes.

2 ) Is “””long strings””” the way to go subject to a discussion about the precise delimiter

Yes.

3) Is the “magic" leading whitespace removal a good idea to support indentation.

Yes.

4) Does the proposal contain sufficient detail to be discussed/implemented

Thanks for the update! I only have the following issues left:

> All other escapes would be processed as before including interpolation, \n and "
You probably meant \“ instead of " here.

The proposal should state what kind of newline will be used within a multiline string literal. I already proposed that it should be exactly the same as for \n and not the newline character(s) actually used in the file (e.g. LF+CR or LF or CR), to avoid issues when working on different platforms (Windows, Mac, Linux) and/or using Git’s autocrlf feature.

The proposal should give an example how to create a multiline string literal which ends with a newline (AFAIU there should be an empty line before the closing ""“).

-Thorsten

My answer to 1) is obviously yes and I think the discussion has come out about 50/50 so far so lets persevere...

Trying to fie down 2), a “””long string””” or @“long string”@ or _”long string”_ or #”long string”# is a string literal inside a new delimiter. It would be processed exactly as it would a normal string including escapes and interpolation except the string can include unescaped “ or “" and newlines. Also, a \ at the end of the line would mean that particular newline is not included in the string.

For me, the goals of a long string are that it should be able to pasted in (almost) without modification from a text source and that syntax highlighting would work for the widest possible range of text editors and github. “””long string””” is just a trick Python uses to satisfy the second goal (for example this gist <Multi-line String Zoo · GitHub) but highlighting also works for asymmetric delimiters such as @“long string”@ which avoid potential problems with “inversion”. Heredoc or a Swifty #equivalent does not satisfy this second goal at all well and IMHO it should be excluded. It would also be significantly more difficult to integrate into the Swift compiler.

Looking at 3) which is underspecified in the proposal perhaps, I’d consider it a “feature" but I can see it would be too magical for some. To specify it more you could say: if there is only whitespace between the last newline and the end of a multiline literal this whitespace will be stripped from all lines in the literal. If lines do not start with this exact sequence of whitespace a warning is emitted. In addition, if the first character in the literal is a newline it will be removed. This operation could be made explicit e.g. #trimLeft(“”"a literal""")

Perhaps we can find common ground on 1) and 2) and even 3) with a view to resubmitting if there is time. Seems mostly like we just need to discuss the delimiter further and decide whether the indent trimming is a bug or a feature to keep moving and not let another year slip by.

With respect to 4) I’m updating https://github.com/johnno1962a/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md as the proposal is discussed to fill in some of the gaps & I’ve prepared a toolchain for Swift 3 if you want to try an implementation out <http://johnholdsworth.com/swift-LOCAL-2017-04-09-a-osx.tar.gz&gt;

On 9 Apr 2017, at 15:35, Thorsten Seitz via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

https://github.com/apple/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md
What is your evaluation of the proposal?

+1

My foremost expectation from multiline string literals is to be able to copy and paste multiline string literals without having to fiddle with escape marks or leading and trailing quotes or continuation characters. This is exactly what the proposal provides and makes it easy to embed SQL, for example (using SQL parameters and not string interpolation of course ;-)

The chosen deindentation rules seem very pragmatic and useful to me.

Additional features for multiline string literals can be added easily later.

I would expect multiline string literals to use the same newline character as "\n“ does, regardless of the newline character actually used in the file.
Furthermore all normal escapes, e.g. \n, \t etc. should probably be available as well.
This should be stated explicitly in the proposal.

Is the problem being addressed significant enough to warrant a change to Swift?

Yes.

Does this proposal fit well with the feel and direction of Swift?

Yes.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

For setting the ground it compares favourably.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Followed most discussions, read the proposal.

-Thorsten

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I feel discussion on this proposal is nearing general agreement that while the gist of it
it seems reasonable to many there is also not enough detail in it as submitted.

Brent has person-fully stepped in and rewritten the draft to translate it into a detailed
specification which I’ve updated here:

https://github.com/johnno1962a/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md

Thank you for the detailed proposal.
FWIW, in general I support it, but have two objections/questions:

1. "Tripled string literals support backslash escapes and interpolation as normal, except that you can also place a backslash immediately before a newline. This indicates that the newline is merely for code formatting and should not be present in the resulting string:"
- I think this a confusion point which should not be in this proposal. Currently in "normal" strings we just can't have single backlash as it will generate "invalid escape sequence in literal" warning.
Is this feature worth the added complexity? I'm not sure currently.

2. Seems like a mistake:
"Multi-line string with indentation stripping prevented by whitespace before leading newline"

"""↵
····Hello↵
····world!"""

Creates a string with:

····↵
····Hello↵
····world!

I believe it should be just:

····Hello↵
····world!

···

On 11.04.2017 16:35, John Holdsworth via swift-evolution wrote:

Perhaps we could use the remaining time on the review to pre-evaluate this draft should
we get the chance to resubmit with modifications to maximise its chances. Key questions:

1) Does Swift need multi-line string literals?
2 ) Is “””long strings””” the way to go subject to a discussion about the precise
delimiter
3) Is the “magic" leading whitespace removal a good idea to support indentation.

The main change on the revised proposal from the the original submitted is Thorsten’s
suggestion that Windows line endings in a source file be normalised to \n in the literal.

John

On 10 Apr 2017, at 12:09, Thorsten Seitz <tseitz42@icloud.com >> <mailto:tseitz42@icloud.com>> wrote:

Am 09.04.2017 um 18:29 schrieb John Holdsworth <mac@johnholdsworth.com
<mailto:mac@johnholdsworth.com>>:

Hi, John here, the submitter of the proposal.

First up, I must apologise for putting Brent on the spot when I resubmitted this
altered proposal from last year. That was my mistake.

Second up, apologies if the proposal is rather vague on details. In some sense
this was intentional as I didn’t want to get too bogged down in specifics (and not
at all to do with my limitations as a technical writer!)

I guess we need to build up consensus more slowly by asking the following
questions separately so it can be resubmitted rather than giving a binary +/-1 on
the proposal as it stands.

1) Does Swift need multi-line string literals?

Yes.

2 ) Is “””long strings””” the way to go subject to a discussion about the precise
delimiter

Yes.

3) Is the “magic" leading whitespace removal a good idea to support indentation.

Yes.

4) Does the proposal contain sufficient detail to be discussed/implemented

Thanks for the update! I only have the following issues left:

> All other escapes would be processed as before including interpolation, \n and "
You probably meant \“ instead of " here.

The proposal should state what kind of newline will be used within a multiline
string literal. I already proposed that it should be exactly the same as for \n and
not the newline character(s) actually used in the file (e.g. LF+CR or LF or CR), to
avoid issues when working on different platforms (Windows, Mac, Linux) and/or using
Git’s autocrlf feature.

The proposal should give an example how to create a multiline string literal which
ends with a newline (AFAIU there should be an empty line before the closing ""“).

-Thorsten

My answer to 1) is obviously yes and I think the discussion has come out about
50/50 so far so lets persevere...

Trying to fie down 2), a “””long string””” or @“long string”@ or _”long string”_
or #”long string”# is a string literal inside a new delimiter. It would be
processed exactly as it would a normal string including escapes and interpolation
except the string can include unescaped “ or “" and newlines. Also, a \ at the
end of the line would mean that particular newline is not included in the string.

For me, the goals of a long string are that it should be able to pasted in
(almost) without modification from a text source and that syntax highlighting
would work for the widest possible range of text editors and github. “””long
string””” is just a trick Python uses to satisfy the second goal (for example this
gist
<Multi-line String Zoo · GitHub) but
highlighting also works for asymmetric delimiters such as @“long string”@ which
avoid potential problems with “inversion”. Heredoc or a Swifty #equivalent does
not satisfy this second goal at all well and IMHO it should be excluded. It would
also be significantly more difficult to integrate into the Swift compiler.

Looking at 3) which is underspecified in the proposal perhaps, I’d consider it a
“feature" but I can see it would be too magical for some. To specify it more you
could say: if there is only whitespace between the last newline and the end of a
multiline literal this whitespace will be stripped from all lines in the literal.
If lines do not start with this exact sequence of whitespace a warning is emitted.
In addition, if the first character in the literal is a newline it will be
removed. This operation could be made explicit e.g. #trimLeft(“”"a literal""")

Perhaps we can find common ground on 1) and 2) and even 3) with a view to
resubmitting if there is time. Seems mostly like we just need to discuss the
delimiter further and decide whether the indent trimming is a bug or a feature to
keep moving and not let another year slip by.

With respect to 4) I’m
updating https://github.com/johnno1962a/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md as
the proposal is discussed to fill in some of the gaps & I’ve prepared a toolchain
for Swift 3 if you want to try an implementation out
<http://johnholdsworth.com/swift-LOCAL-2017-04-09-a-osx.tar.gz&gt;

On 9 Apr 2017, at 15:35, Thorsten Seitz via swift-evolution >>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

    https://github.com/apple/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md

What is your evaluation of the proposal?

+1

My foremost expectation from multiline string literals is to be able to copy and
paste multiline string literals without having to fiddle with escape marks or
leading and trailing quotes or continuation characters. This is exactly what the
proposal provides and makes it easy to embed SQL, for example (using SQL
parameters and not string interpolation of course ;-)

The chosen deindentation rules seem very pragmatic and useful to me.

Additional features for multiline string literals can be added easily later.

I would expect multiline string literals to use the same newline character as
"\n“ does, regardless of the newline character actually used in the file.
Furthermore all normal escapes, e.g. \n, \t etc. should probably be available as
well.
This should be stated explicitly in the proposal.

Is the problem being addressed significant enough to warrant a change to Swift?

Yes.

Does this proposal fit well with the feel and direction of Swift?

Yes.

If you have used other languages or libraries with a similar feature, how do you
feel that this proposal compares to those?

For setting the ground it compares favourably.

How much effort did you put into your review? A glance, a quick reading, or an
in-depth study?

Followed most discussions, read the proposal.

-Thorsten

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

That’s a great revision, Brent! Thanks a lot!

I like it as written but would also be totally fine with Xiaodi’s proposition of stripping the trailing newline by default (requiring an empty line for a trailing newline).

-Thorsten

···

Am 11.04.2017 um 15:35 schrieb John Holdsworth <mac@johnholdsworth.com>:

I feel discussion on this proposal is nearing general agreement that while the gist of it
it seems reasonable to many there is also not enough detail in it as submitted.

Brent has person-fully stepped in and rewritten the draft to translate it into a detailed
specification which I’ve updated here:

https://github.com/johnno1962a/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md

Perhaps we could use the remaining time on the review to pre-evaluate this draft should
we get the chance to resubmit with modifications to maximise its chances. Key questions:

1) Does Swift need multi-line string literals?
2 ) Is “””long strings””” the way to go subject to a discussion about the precise delimiter
3) Is the “magic" leading whitespace removal a good idea to support indentation.

The main change on the revised proposal from the the original submitted is Thorsten’s
suggestion that Windows line endings in a source file be normalised to \n in the literal.

John

On 10 Apr 2017, at 12:09, Thorsten Seitz <tseitz42@icloud.com <mailto:tseitz42@icloud.com>> wrote:

Am 09.04.2017 um 18:29 schrieb John Holdsworth <mac@johnholdsworth.com <mailto:mac@johnholdsworth.com>>:

Hi, John here, the submitter of the proposal.

First up, I must apologise for putting Brent on the spot when I resubmitted this altered proposal from last year. That was my mistake.

Second up, apologies if the proposal is rather vague on details. In some sense this was intentional as I didn’t want to get too bogged down in specifics (and not at all to do with my limitations as a technical writer!)

I guess we need to build up consensus more slowly by asking the following questions separately so it can be resubmitted rather than giving a binary +/-1 on the proposal as it stands.

1) Does Swift need multi-line string literals?

Yes.

2 ) Is “””long strings””” the way to go subject to a discussion about the precise delimiter

Yes.

3) Is the “magic" leading whitespace removal a good idea to support indentation.

Yes.

4) Does the proposal contain sufficient detail to be discussed/implemented

Thanks for the update! I only have the following issues left:

> All other escapes would be processed as before including interpolation, \n and "
You probably meant \“ instead of " here.

The proposal should state what kind of newline will be used within a multiline string literal. I already proposed that it should be exactly the same as for \n and not the newline character(s) actually used in the file (e.g. LF+CR or LF or CR), to avoid issues when working on different platforms (Windows, Mac, Linux) and/or using Git’s autocrlf feature.

The proposal should give an example how to create a multiline string literal which ends with a newline (AFAIU there should be an empty line before the closing ""“).

-Thorsten

My answer to 1) is obviously yes and I think the discussion has come out about 50/50 so far so lets persevere...

Trying to fie down 2), a “””long string””” or @“long string”@ or _”long string”_ or #”long string”# is a string literal inside a new delimiter. It would be processed exactly as it would a normal string including escapes and interpolation except the string can include unescaped “ or “" and newlines. Also, a \ at the end of the line would mean that particular newline is not included in the string.

For me, the goals of a long string are that it should be able to pasted in (almost) without modification from a text source and that syntax highlighting would work for the widest possible range of text editors and github. “””long string””” is just a trick Python uses to satisfy the second goal (for example this gist <Multi-line String Zoo · GitHub) but highlighting also works for asymmetric delimiters such as @“long string”@ which avoid potential problems with “inversion”. Heredoc or a Swifty #equivalent does not satisfy this second goal at all well and IMHO it should be excluded. It would also be significantly more difficult to integrate into the Swift compiler.

Looking at 3) which is underspecified in the proposal perhaps, I’d consider it a “feature" but I can see it would be too magical for some. To specify it more you could say: if there is only whitespace between the last newline and the end of a multiline literal this whitespace will be stripped from all lines in the literal. If lines do not start with this exact sequence of whitespace a warning is emitted. In addition, if the first character in the literal is a newline it will be removed. This operation could be made explicit e.g. #trimLeft(“”"a literal""")

Perhaps we can find common ground on 1) and 2) and even 3) with a view to resubmitting if there is time. Seems mostly like we just need to discuss the delimiter further and decide whether the indent trimming is a bug or a feature to keep moving and not let another year slip by.

With respect to 4) I’m updating https://github.com/johnno1962a/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md as the proposal is discussed to fill in some of the gaps & I’ve prepared a toolchain for Swift 3 if you want to try an implementation out <http://johnholdsworth.com/swift-LOCAL-2017-04-09-a-osx.tar.gz&gt;

On 9 Apr 2017, at 15:35, Thorsten Seitz via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

https://github.com/apple/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md
What is your evaluation of the proposal?

+1

My foremost expectation from multiline string literals is to be able to copy and paste multiline string literals without having to fiddle with escape marks or leading and trailing quotes or continuation characters. This is exactly what the proposal provides and makes it easy to embed SQL, for example (using SQL parameters and not string interpolation of course ;-)

The chosen deindentation rules seem very pragmatic and useful to me.

Additional features for multiline string literals can be added easily later.

I would expect multiline string literals to use the same newline character as "\n“ does, regardless of the newline character actually used in the file.
Furthermore all normal escapes, e.g. \n, \t etc. should probably be available as well.
This should be stated explicitly in the proposal.

Is the problem being addressed significant enough to warrant a change to Swift?

Yes.

Does this proposal fit well with the feel and direction of Swift?

Yes.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

For setting the ground it compares favourably.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Followed most discussions, read the proposal.

-Thorsten

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Yes, you are right. I tested using the IBM Swift Sandbox <https://swift.sandbox.bluemix.net/#/repl&gt;\.

In Xcode the output is as expected with the empty line in between the two lines.

···

On Apr 12, 2017, at 2:33 PM, Adrian Zubarev <adrian.zubarev@devandartist.com> wrote:

You’re wrong there, Xcode does print this for me:

Line1

Line 2
The new line is there as expected.

--
Adrian Zubarev
Sent with Airmail

Am 12. April 2017 um 20:30:00, Ricardo Parada (rparada@mac.com <mailto:rparada@mac.com>) schrieb:

print("Line1\n")
print("Line 2")

John, why do you think that option 2 is superior to option 3?

···

On Wed, Apr 12, 2017 at 16:14 John Holdsworth via swift-evolution < swift-evolution@swift.org> wrote:

I think we’re agreeing. Looks like I need to clarify my last post a
little. When I included the following strings:

        let longstring = """\
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod \
            tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
minim veniam, \
            quis nostrud exercitation ullamco laboris nisi ut aliquip ex
ea commodo consequat.\
            """

        print( """\
            Usage: myapp <options>

            Run myapp to do mything

            Options:
            -myoption - an option
            """ )

These were expressed in term of the proposal after last nights changes.

By advocating option 2) I’m accepting we should revert back to the
following:

        let longstring = """
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod \
            tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
minim veniam, \
            quis nostrud exercitation ullamco laboris nisi ut aliquip ex
ea commodo consequat.\
            """

        print( """
            Usage: myapp <options>

            Run myapp to do mything

            Options:
            -myoption - an option
            ""” )

1) Proposal as it stands - no magic removal of leading/training blank
lines.
2) Removal of a leading blank line when indent stripping is being applied.
3) Removal of leading blank line and trailing newline when indent
stripping is being applied.

Also, note: the toolchain does not seem to work at all with playgrounds.
I’ve been using small test apps.

On 12 Apr 2017, at 21:06, Adrian Zubarev via swift-evolution < > swift-evolution@swift.org> wrote:

Exactly, I feel like we found a Swifty version of a multi-line string
literal. :)

--
Adrian Zubarev
Sent with Airmail

Am 12. April 2017 um 21:58:37, Vladimir.S via swift-evolution (
swift-evolution@swift.org) schrieb:

this:
"""
one
two
"""
should be just the same as "one\ntwo\n"

If one wants some advanced tuning of line ends, he/she can use a
backslash, for example
"""
one
two\
"""
should produce "one\ntwo"

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Quickly correcting myself. I meant to say:

As already mentioned a couple times the rules can be simplified by disallowing text in the same line after and before the starting/closing delimiters.

···

--
Adrian Zubarev
Sent with Airmail

Am 12. April 2017 um 21:34:34, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

As already mentioned a couple times the rules can be simplified by disallowing text after and before the starting/leading delimiters.

Sorry, what do you mean by "pure" or "practical"?

···

On Wed, Apr 12, 2017 at 16:41 John Holdsworth <mac@johnholdsworth.com> wrote:

There isn’t much in it TBH and I could live with either. Option 1 seems to
have been a regression.

Option 3 is the pure route in one sense but for me Option 2 the more
practical which I
was hoping to demonstrate with the example strings. I’d also ague lines
should be
complete (have line endings unless escaped) by default in a multiline
string.

On 12 Apr 2017, at 22:31, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

John, why do you think that option 2 is superior to option 3?

On Wed, Apr 12, 2017 at 16:14 John Holdsworth via swift-evolution < > swift-evolution@swift.org> wrote:

I think we’re agreeing. Looks like I need to clarify my last post a
little. When I included the following strings:

        let longstring = """\
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod \
            tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
minim veniam, \
            quis nostrud exercitation ullamco laboris nisi ut aliquip ex
ea commodo consequat.\
            """

        print( """\
            Usage: myapp <options>

            Run myapp to do mything

            Options:
            -myoption - an option
            """ )

These were expressed in term of the proposal after last nights changes.

By advocating option 2) I’m accepting we should revert back to the
following:

        let longstring = """
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod \
            tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
minim veniam, \
            quis nostrud exercitation ullamco laboris nisi ut aliquip ex
ea commodo consequat.\
            """

        print( """
            Usage: myapp <options>

            Run myapp to do mything

            Options:
            -myoption - an option
            ""” )

1) Proposal as it stands - no magic removal of leading/training blank
lines.
2) Removal of a leading blank line when indent stripping is being applied.
3) Removal of leading blank line and trailing newline when indent
stripping is being applied.

Also, note: the toolchain does not seem to work at all with playgrounds.
I’ve been using small test apps.

On 12 Apr 2017, at 21:06, Adrian Zubarev via swift-evolution < > swift-evolution@swift.org> wrote:

Exactly, I feel like we found a Swifty version of a multi-line string
literal. :)

--
Adrian Zubarev
Sent with Airmail

Am 12. April 2017 um 21:58:37, Vladimir.S via swift-evolution (
swift-evolution@swift.org) schrieb:

this:
"""
one
two
"""
should be just the same as "one\ntwo\n"

If one wants some advanced tuning of line ends, he/she can use a
backslash, for example
"""
one
two\
"""
should produce "one\ntwo"

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Exactly, I feel like we found a Swifty version of a multi-line string literal. :)

···

--
Adrian Zubarev
Sent with Airmail

Am 12. April 2017 um 21:58:37, Vladimir.S via swift-evolution (swift-evolution@swift.org) schrieb:

this:
"""
one
two
"""
should be just the same as "one\ntwo\n"

If one wants some advanced tuning of line ends, he/she can use a backslash, for example
"""
one
two\
"""
should produce "one\ntwo"

I think we’re agreeing. Looks like I need to clarify my last post a little. When I included the following strings:

        let longstring = """\
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \
            tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \
            quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\
            """

        print( """\
            Usage: myapp <options>
            
            Run myapp to do mything
            
            Options:
            -myoption - an option
            """ )

These were expressed in term of the proposal after last nights changes.

By advocating option 2) I’m accepting we should revert back to the following:

        let longstring = """
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \
            tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \
            quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\
            """

        print( """
            Usage: myapp <options>
            
            Run myapp to do mything
            
            Options:
            -myoption - an option
            ""” )

1) Proposal as it stands - no magic removal of leading/training blank lines.
2) Removal of a leading blank line when indent stripping is being applied.
3) Removal of leading blank line and trailing newline when indent stripping is being applied.

Also, note: the toolchain does not seem to work at all with playgrounds. I’ve been using small test apps.

···

On 12 Apr 2017, at 21:06, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Exactly, I feel like we found a Swifty version of a multi-line string literal. :)

--
Adrian Zubarev
Sent with Airmail

Am 12. April 2017 um 21:58:37, Vladimir.S via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

this:
"""
one
two
"""
should be just the same as "one\ntwo\n"

If one wants some advanced tuning of line ends, he/she can use a backslash, for example
"""
one
two\
"""
should produce "one\ntwo"

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

There isn’t much in it TBH and I could live with either. Option 1 seems to have been a regression.

Option 3 is the pure route in one sense but for me Option 2 the more practical which I
was hoping to demonstrate with the example strings. I’d also ague lines should be
complete (have line endings unless escaped) by default in a multiline string.

···

On 12 Apr 2017, at 22:31, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

John, why do you think that option 2 is superior to option 3?

On Wed, Apr 12, 2017 at 16:14 John Holdsworth via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I think we’re agreeing. Looks like I need to clarify my last post a little. When I included the following strings:

        let longstring = """\
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \
            tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \
            quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\
            """

        print( """\
            Usage: myapp <options>
            
            Run myapp to do mything
            
            Options:
            -myoption - an option
            """ )

These were expressed in term of the proposal after last nights changes.

By advocating option 2) I’m accepting we should revert back to the following:

        let longstring = """
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \
            tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \
            quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\
            """

        print( """
            Usage: myapp <options>
            
            Run myapp to do mything
            
            Options:
            -myoption - an option
            ""” )

1) Proposal as it stands - no magic removal of leading/training blank lines.
2) Removal of a leading blank line when indent stripping is being applied.
3) Removal of leading blank line and trailing newline when indent stripping is being applied.

Also, note: the toolchain does not seem to work at all with playgrounds. I’ve been using small test apps.

On 12 Apr 2017, at 21:06, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Exactly, I feel like we found a Swifty version of a multi-line string literal. :)

--
Adrian Zubarev
Sent with Airmail

Am 12. April 2017 um 21:58:37, Vladimir.S via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

this:
"""
one
two
"""
should be just the same as "one\ntwo\n"

If one wants some advanced tuning of line ends, he/she can use a backslash, for example
"""
one
two\
"""
should produce "one\ntwo"

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Below you can read a part of my message to John, where I was testing the toolchain.

If that design for the trailing spaces is indented then I disagree with that design choice.

In a normal string literal it’s crystal clear that it contains multiple space characters at the end, because it’s visible to the reader of that code. As shown below, if Xcode wouldn’t help here, the end result might contain spaces which could be caused by an accident and are not clear to the reader at all.

I suggest to align this behavior with normal string literals by stripping all space characters at the end of each line, just like Xcode does, unless they are explicitly annotated by a backslash, which would be clear to the reader of the code.

That said, this:

let s0 = "abc\n"

let s1 = """↵
    abc···↵
    """
     
let s2 = """↵
    abc···\↵
    """
     
let s3 = """↵
    abc···\n\↵
    """
     
s0 == s1 // => true
s0 == s2 // => false
s2 == "abc···" // => true
s3 == "abc···\n" // => true
Xcode does trip trailing spaces by default for me, however I was able to press spacebar a couple times and these spaces where not trimmed by the current toolchain.

“”"↵
AA····↵
“”"

The string was equivalent to “\nAA····\n”

The strange character is actually a white space displayed by Xcode.

27DCFF63-7467-42FC-901F-0B085EB37DBD (19.1 KB)

···

--
Adrian Zubarev
Sent with Airmail

Am 12. April 2017 um 22:06:01, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

Exactly, I feel like we found a Swifty version of a multi-line string literal. :)

--
Adrian Zubarev
Sent with Airmail

Am 12. April 2017 um 21:58:37, Vladimir.S via swift-evolution (swift-evolution@swift.org) schrieb:

this:
"""
one
two
"""
should be just the same as "one\ntwo\n"

If one wants some advanced tuning of line ends, he/she can use a backslash, for example
"""
one
two\
"""
should produce "one\ntwo"