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

Vladimir.S svabox at gmail.com
Wed Apr 12 08:41:04 CDT 2017


On 12.04.2017 15:59, Tony Allevato via swift-evolution wrote:
> I also would oppose comments inside multi-line strings because one place I imagine 
> using it is in Swift code generation and I also want to generate comments, and it 
> seems pointless to have to escape those.
> 
> Let's not over-engineer this and end up with feature creep. A simple multi-line 
> string that takes its contents more or less verbatim (with the exception of necessary 
> escapes, interpolation, and dedenting) is fine.

Agree. Even more, we can have comments on separate lines:

var s = """
	text
	text
	""""
	// value will be inserted here
	+ """
	value : \(value)
	""""
	// other comment
	+ """
	text
	text
	"""

> On Wed, Apr 12, 2017 at 5:50 AM Xiaodi Wu via swift-evolution 
> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> 
>     Right, I think it might be too much.
> 
>     Consider that the multi-line literal could be code with comments in it, but that
>     you could also escape and embed an actual multi-line comment with \ /* */ that
>     isn't part of the multi-line literal code with multi-line comments! How confusing!
>     On Wed, Apr 12, 2017 at 07:36 Ricardo Parada via swift-evolution
>     <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> 
>         I don't think I would use that. I don't find the aesthetics pleasant.
>         I would rather comment above the string literal.
> 
>         Would the escape character cause the newline for the line to be ignored
>         thereby continuing the string on the next line?
> 
> 
> 
>         On Apr 12, 2017, at 6:59 AM, Adrian Zubarev via swift-evolution
>         <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> 
>>         One last pitch, can we allow comments in multi-line strings if the string
>>         is broken up by a backslash?
>>
>>         |let myString = """ text text text text text \ // Comment allowed in the
>>         current line here, but not in the line above it text text text \ /* this
>>         type of comment is fine too */ text text\// notice whitespace can be
>>         ignored """ |
>>
>>         You might have some interpolation and want to comment around it.
>>
>>         |let foo = """ bar bar bar bar \(x) bar\ // `x` does some magic """ |
>>
>>
>>
>>         -- 
>>         Adrian Zubarev
>>         Sent with Airmail
>>
>>         Am 12. April 2017 um 12:48:57, Adrian Zubarev
>>         (adrian.zubarev at devandartist.com <mailto:adrian.zubarev at devandartist.com>)
>>         schrieb:
>>
>>>         Actually I’m fine with such a compromise. Such a model has everything
>>>         we’ve asked for, it’s easy, it has both leading and trailing precision and
>>>         implicit new lines where needed.
>>>
>>>
>>>
>>>         -- 
>>>         Adrian Zubarev
>>>         Sent with Airmail
>>>
>>>         Am 12. April 2017 um 12:42:17, Vladimir.S via swift-evolution
>>>         (swift-evolution at swift.org <mailto:swift-evolution at swift.org>) schrieb:
>>>
>>>>         On 12.04.2017 13:16, Thorsten Seitz via swift-evolution wrote:
>>>>         >> Am 12.04.2017 um 10:11 schrieb Adrian Zubarev via swift-evolution
>>>>         >> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>         <mailto:swift-evolution at swift.org>>:
>>>>         >>
>>>>         >> Great explanation thank you Brent. I’m convinced about the closing delimiter now. =)
>>>>         >>
>>>>         >> -------------------------------------------------------------------------------------
>>>>         >>
>>>>         >> If I understood correctly what Xiaodi Wu meant in his reply, then we could simplify
>>>>         >> the whole multi-line string literal and also remove the need of disabling the
>>>>         >> stripping algorithm.
>>>>         >>
>>>>         >> We should ban these examples completely:
>>>>         >>
>>>>         >> |"""Hello·world!"""|
>>>>         >>
>>>>         >
>>>>         > Being able to use ""“ for single line strings containing lots of " is useful in
>>>>         > itself and explained in the motivational section of the proposal:
>>>>         > "Tripled string literals can also do double duty as a syntax for handling short
>>>>         > string literals with many internal quotation marks“
>>>>         >
>>>>         > -Thorsten
>>>>
>>>>         Yes, I also think the single line string can be very useful and we should not
>>>>         disallow it.
>>>>
>>>>         But I agree that we should disallow multi-line cases when we have text on
>>>>         the same
>>>>         line with leading or trailing """ because this complicates the mental
>>>>         modal and adds
>>>>         confusion points.
>>>>
>>>>         I.e. I suggest to allow only two forms:
>>>>         1. Single line: """this is "just" text""" (no line end will be inserted)
>>>>         2. Multiline, where leading and trailing """ has no text after/before
>>>>         them and *all*
>>>>         the text is in lines *between* triple quotes:
>>>>         """
>>>>         first line
>>>>         second line
>>>>         """
>>>>
>>>>         One can use backslash at the line end to emulate all other needed cases.
>>>>         Like:
>>>>
>>>>         """
>>>>         first line \
>>>>         second line\
>>>>         """
>>>>
>>>>         will produce "first line second line"
>>>>
>>>>         >
>>>>         >> |"""Hello↵ world!""" |
>>>>         >> |"""Hello↵ world!↵ """ |
>>>>         >> |"""↵ Hello↵ world!""" |
>>>>         >>
>>>>         >> Instead an empty multi-line string literal would look like this:
>>>>         >>
>>>>         >> |"""↵ """ |
>>>>         >>
>>>>         >> To fix the above example you’d need to write it like this:
>>>>         >>
>>>>         >> |"""↵ Hello·world!\↵ """ |
>>>>         >> |"""↵ Hello↵ world!\↵ """ |
>>>>         >>
>>>>         >> * Each line in between the delimiters would add implicit new lines if not
>>>>         >> disabled by a backslash.
>>>>         >> * The trailing precision is also handled by the backslash.
>>>>         >> * The indent is handled by the closing delimiter.
>>>>         >> * It’s easier to learn/teach.
>>>>         >> * It’s easier to read, because most of the time the line where the starting
>>>>         >> delimiter is, is filled with some other code.
>>>>         >>
>>>>         >> |let myString = """↵ ⇥ ⇥ Hello↵ ⇥ ⇥ world!\↵ ⇥ ⇥ """ |
>>>>         >>
>>>>         >> Now that would be a true multi-line string literal which needs at least two lines
>>>>         >> of code. If you’d need a single line literal,|""|is the obvious pick.
>>>>         >>
>>>>         >>
>>>>         >>
>>>>         >>
>>>>         >> --
>>>>         >> Adrian Zubarev
>>>>         >> Sent with Airmail
>>>>         >>
>>>>         >> Am 12. April 2017 um 02:32:33, Brent Royal-Gordon (brent at architechies.com <mailto:brent at architechies.com>
>>>>         >> <mailto:brent at architechies.com>) schrieb:
>>>>         >>
>>>>         >>>
>>>>         >>>> On Apr 11, 2017, at 8:08 AM, Adrian Zubarev via swift-evolution
>>>>         >>>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>         <mailto:swift-evolution at swift.org>> wrote:
>>>>         >>>>
>>>>         >>>> That’s also the example that kept me thinking for a while.
>>>>         >>>>
>>>>         >>>> -------------------------------------------------------------------------------------
>>>>         >>>>
>>>>         >>>> Overall the proposal is a great compromise to some issues I had with the first
>>>>         >>>> version. However I have a few more questions:
>>>>         >>>>
>>>>         >>>> * Why can’t we make it consistent and let the compiler add a new line after the
>>>>         >>>> starting delimiter.
>>>>         >>>>
>>>>         >>>> |
let string = """↵ Swift↵ """ // result ↵Swift↵ |
>>>>         >>>>
>>>>         >>>> If one would would the behavior from the proposal it’s really easy to add a
>>>>         >>>> backslash after the starting delimiter.
>>>>         >>>>
>>>>         >>>> |
let string = """\↵ Swift\↵ """ // result Swift |
>>>>         >>>>
>>>>         >>>> This would be consistent and less confusing to learn.
>>>>         >>>>
>>>>         >>> That would mean that code like this:
>>>>         >>>
>>>>         >>> print("""
>>>>         >>> A whole bunch of
>>>>         >>> multiline text
>>>>         >>> """)
>>>>         >>> print("""
>>>>         >>> A whole bunch more
>>>>         >>> multiline text
>>>>         >>> """)
>>>>         >>>
>>>>         >>> Will print (with - to indicate blank lines):
>>>>         >>>
>>>>         >>> -
>>>>         >>> A whole bunch of
>>>>         >>> multiline text
>>>>         >>> -
>>>>         >>> -
>>>>         >>> A whole bunch more
>>>>         >>> multiline text
>>>>         >>> -
>>>>         >>>
>>>>         >>> This is, to a first approximation, never what you actually want the computer to do.
>>>>         >>>>
>>>>         >>>> * Can’t we make the indent algorithm work like this instead?
>>>>         >>>>
>>>>         >>>> |let string = """\↵ ····<tag>↵ ······content text↵ ····</tag>""" // Indent starts
>>>>         >>>> with the first non space character // result <tag>↵ ··content text↵ </tag> |
>>>>         >>>>
>>>>         >>>> The line where the closing delimiter is trims all space chapters and the indent
>>>>         >>>> for the whole multi-line string is starting at the point where the first
>>>>         >>>> non-space chapters is in that line.
>>>>         >>>>
>>>>         >>> We could; I discuss that briefly in the very last section, on alternatives to the
>>>>         >>> indentation stripping we specify:
>>>>         >>>
>>>>         >>> • Stripping indentation to match the depth of the least indented line: Instead of
>>>>         >>> removing indentation to match the end delimiter, you remove indentation to match
>>>>         >>> the least indented line of the string itself. The issue here is that, if all lines
>>>>         >>> in a string should be indented, you can't use indentation stripping. Ruby 2.3 does
>>>>         >>> this with its heredocs, and Python's dedent function also implements this behavior.
>>>>         >>>
>>>>         >>> That doesn't quite capture the entire breadth of the problem with this algorithm,
>>>>         >>> though. What you'd like to do is say, "all of these lines are indented four
>>>>         >>> columns, so we should remove four columns of indentation from each line". But you
>>>>         >>> don't have columns; you have tabs and spaces, and they're incomparable because the
>>>>         >>> compiler can't know what tab stops you set. So we'd end up calculating a common
>>>>         >>> prefix of whitespace for all lines and removing that. But that means, when someone
>>>>         >>> mixes tabs and spaces accidentally, you end up stripping an amount of indentation
>>>>         >>> that is unrelated to anything visible in your code. We could perhaps emit a
>>>>         >>> warning in some suspicious circumstances (like "every line has whitespace just
>>>>         >>> past the end of indentation, but some use tabs and others use spaces"), but if we
>>>>         >>> do, we can't know which one is supposed to be correct. With the proposed design,
>>>>         >>> we know what's correct—the last line—and any deviation from it can be flagged *at
>>>>         >>> the particular line which doesn't match our expectation*.
>>>>         >>>
>>>>         >>> Even without the tabs and spaces issue, consider the case where you accidentally
>>>>         >>> don't indent a line far enough. With your algorithm, that's indistinguishable from
>>>>         >>> wanting the other lines to be indented more than that one, so we generate a result
>>>>         >>> you don't want and we don't (can't!) emit a warning to point out the mistake. With
>>>>         >>> the proposed algorithm, we can notice there's an error and point to the line at fault.
>>>>         >>>
>>>>         >>> Having the closing delimiter always be on its own line and using it to decide how
>>>>         >>> much whitespace to strip is better because it gives the compiler a firm baseline
>>>>         >>> to work from. That means it can tell you what's wrong and where, instead of doing
>>>>         >>> the dumb computer thing and computing a result that's technically correct but useless.
>>>>         >>>>
>>>>         >>>> PS: If we’d get this feature in Swift, it would be nice if Xcode and other IDEs
>>>>         >>>> which supports Swift could show space characters that are inside a string literal
>>>>         >>>> (not other space character <- which is already supported), so it would be easier
>>>>         >>>> to tell what’s part of the string and what is not.
>>>>         >>>>
>>>>         >>> That would be very nice indeed. The prototype's tokenizer simply concatenates
>>>>         >>> together and computes the string literal's contents after whitespace stripping,
>>>>         >>> but in principle, I think it could probably preserve enough information to tell
>>>>         >>> SourceKit where the indentation ends and the literal content begins. (The
>>>>         >>> prototype is John's department, though, not mine.) Xcode would then have to do
>>>>         >>> something with that information, though, and swift-evolution can't make the Xcode
>>>>         >>> team do so. But I'd love to see a faint reddish background behind tripled string
>>>>         >>> literal content or a vertical line at the indentation boundary.
>>>>         >>>
>>>>         >>> In the meantime, this design *does* provide an unambiguous indicator of how much
>>>>         >>> whitespace will be trimmed: however much is to the left of the closing delimiter.
>>>>         >>> You just have to imagine the line extending upwards from there. I think that's an
>>>>         >>> important thing to have.
>>>>         >>>
>>>>         >>> --
>>>>         >>> Brent Royal-Gordon
>>>>         >>> Architechies
>>>>         >>>
>>>>         >>
>>>>         >> _______________________________________________
>>>>         >> swift-evolution mailing list
>>>>         >> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>         <mailto:swift-evolution at swift.org>
>>>>         >> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>         >
>>>>         >
>>>>         >
>>>>         > _______________________________________________
>>>>         > swift-evolution mailing list
>>>>         > swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>         > https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>         >
>>>>         _______________________________________________
>>>>         swift-evolution mailing list
>>>>         swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>         https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>         _______________________________________________
>>         swift-evolution mailing list
>>         swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>         https://lists.swift.org/mailman/listinfo/swift-evolution
>         _______________________________________________
>         swift-evolution mailing list
>         swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>         https://lists.swift.org/mailman/listinfo/swift-evolution
> 
>     _______________________________________________
>     swift-evolution mailing list
>     swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>     https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> 
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
> 


More information about the swift-evolution mailing list