This is an excellent update! The proposed syntax is quite elegant and the revised proposal has improved significantly. I'm very glad to see that alternative delimiters and raw strings have been made orthogonal features. Overall, it feels very "swifty" in its current iteration.
I do want to present a significant alternative solution to the stated problems; I sketched this out briefly in a message to @Erica_Sadun but will flesh it out here. To be clear, I wouldn't be distraught if it isn't acclaimed to be a superior solution: your proposal is excellent. However, I do think that your proposal would be strengthened by considering such an alternative if only to weigh it fully against your own solution--
Consider the five stated use cases of this proposal:
Metaprogramming, regular expressions, pedagogy (specifically: code snippets), formatted data and DSLs, Windows paths.
It is true that all of these benefit from not requiring escaping. However, raw strings (as their name suggests) have fewer syntactic constraints than their "cooked" counterparts, whereas all of these use cases involve contents that have more syntactic constraints than arbitrary free text.
Now, consider the following two features of Swift:
/*
This is a comment.
/* This is a nested comment. */
In C, this wouldn't do the intuitive thing!
*/
#if false
val x = 42 // Error: Consecutive statements on a line must be separated by ';'
// Swift parses inside conditional compilation blocks!
#endif
These examples show that, where possible, Swift helps the user do "the right thing" even in cases where what's written isn't compiled. Other languages don't always do so, and we might rightly regard this as a "swifty" characteristic of the language.
Can we provide the same benefits for the five motivating use cases? I think so. Let's consider a new species of literal, which I will call the code literal:
Just as Double
is expressible by either integer literals or float literals, so too would String
be expressible by either string literals or code literals.
The code literal would take essentially the same syntax as that of code blocks in GitHub-flavored Markdown, with three or more backticks followed optionally by a language name; the same rules would apply as to indent stripping as for multiline string literals:
let x = ```swift
let y = 42
```
In terms of its simplest implementation, there is no need for a code literal to be implemented any differently than a raw string literal. However, several advantages present themselves:
- Ease of reading: For the human reader, it can be immediately discerned that the embedded contents are not free text but rather some structured code block. If a language name is given after the backticks, the reader has a visible marker to tell them how to parse the contents.
- Syntax highlighting: For an editor or IDE that already supports syntax highlighting for multiple languages, a code literal would permit its embedded contents (if written in a supported language) to be correctly highlighted.
- Swift-specific features: For code generation of Swift in Swift, or for code snippets to teach Swift, the compiler can parse the contents of the literal just as it parses the contents of conditional compilation blocks [*]. Besides the benefit of diagnosing some incorrect code, Swift-specific parsing would enable nested code literals without the need for alternative delimiters [**] just as we support nested block comments:
let x = ```swift
let y = ```swift
let z = 42
```
```
[*] This could, of course, be disabled either by not indicating that the code is written in Swift, or by surrounding the embedded code with a conditional compilation block that requires a future Swift version (which disables parsing).
[**] There is no need to abandon alternative delimiters, however; as you propose, it can be an orthogonal feature.
All of these potential enhancements would advance Swift's support for the five stated use cases, not only getting rid of hard-to-read and hard-to-write escape sequences, but also providing other features to help users write what they intend and read what others intended.
Down the road, one might envision library types that are ExpressibleByCodeLiteral
but not ExpressibleByStringLiteral
. Indeed, one might even envision a design where the initializer is spelled init(codeLiteral: String, language: String?)
, and a conforming type could have in its implementation of that initializer a precondition that language
is some particular value; with constexpr
support, this could be a precondition that is checked at compile time.
Is the lack of a single-line syntax for such code literals a major drawback? In my view, not so much. As stated here, "raw strings" are envisioned to be a rarely used feature. In any case, the benefit of not escaping characters really increases with longer strings. Similarly, syntax highlighting and other features provide much greater benefit when the text is longer. Moreover, many of these use cases are likely to require multiple lines much more commonly than single lines of code.