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

Adrian Zubarev adrian.zubarev at devandartist.com
Wed Apr 12 19:00:26 CDT 2017


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 at 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 at 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 = """\
            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 at 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

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170413/c9217ede/attachment.html>


More information about the swift-evolution mailing list