Raw string literals -- where we are, how we got here

Brian Goetz brian.goetz at oracle.com
Tue Mar 27 19:15:24 UTC 2018


Now that things have largely stabilized with raw string literals, let me 
summarize where we are, and how we got here.

## The proposal

Where we are now is that a raw string literal consists of an opening 
delimiter which is a sequence of N consecutive backticks, for some N > 
0, a body which may contain any characters (including newlines) except 
for a sequence of N consecutive backticks, and a closing delimiter of N 
consecutive backticks. Any line-end sequences (CR, LF, CRLF) are 
normalized to a single newline (LF), and the remainder of the body is 
treated without any further transformation (including without unicode 
escape processing), and placed in a String.  No other processing is done 
on the contents.

A raw string literal has type String, just like a traditional string 
literal, and can be used anywhere an expression of type String can be 
used (assignment, concatenation, etc.)

Examples:

     String s = `Doesn't have a \n newline character in it`;
     String ss = `a multi-
         line-string`;
     String sss = ``a string with a single tick (`) character in it``;
     String ssss = `a string with two ticks (``) in it`;
     String sssss = `````a string literal with gratuitously many ticks 
in its delimiter`````;

Note that the delimiter need not be _more_ ticks than the longest tick 
sequence in the body; if the body contains sequences of two ticks and 
three ticks, it can be delimited by one tick, four ticks, five ticks, 
etc.  This makes it possible to choose a minimal delimiter that doesn't 
interfere with the body.

## Design Center

The design center for this feature is _raw string literals_.  Not 
multi-line strings (though this is well handled), not interpolated 
strings (though this can be considered in the future.)  It turns off all 
inline escaping, even unicode escaping (which is usually handled by the 
lexer before the production even sees the characters.) We stay as true 
as we can to this principle: raw means raw, not 99% raw with a little 
bit of escaping.  (The single exception is normalizing of carriage 
control, the absence of which would just be too surprising.)

The primary use case addressed by raw string literals are snippets of 
code from other languages embedded in Java source files.  Here we 
interpret "languages" broadly; they could be traditional programming 
languages, specialized languages like regular expressions or SQL, or 
human languages.  We want that the Java lexing not interfere at all; 
given a suitable O(1) incantation (picking a non-conflicting delimiter), 
you can freely cut and paste the foreign string to and from Java.  Being 
able to do this is not only convenient, but it reduces errors due to 
hand-mangling the string, and enhances readability because the embedded 
snippet is free of interference from Java.

Choosing raw-ness as a design center leads to a simpler design, which is 
good, but it also is _more stable_, because it leads us away from the 
temptation to tweak the rules here and there in ways that might be 
subjectively attractive, but that further increase the complexity of the 
feature.  This design choice belies a priority choice: the high-order 
bit is _no embedding anomalies_. Users don't have to reason about 
whether they need to hand-mangle a snippet to avoid it being mangled by 
the compiler or runtime; given a suitable choice of delimiter, there's 
nothing else to think about.  (IDEs can help with the "writing code" 
part of this.)

The various additional features we might be tempted to put in (special 
processing for leading or trailing blank lines, leading white space, 
trimming to markers, etc) can instead be handled via library 
functionality.  Since raw string literals are Strings, we can further 
process them with library code -- both JDK code and user code (though 
methods on String have the advantage that they can be chained, rather 
than wrapped, which most users will prefer).  Adding new string 
manipulation features via libraries rather than through the language is 
easier, can be done by users, and is not constrained by the demands of 
consistency (you can have seven different trimming methods, each with 
their own definition of whitespace, if you like), whereas a language 
feature has to be one-size-fits-all.  Moving this complexity to the 
library where possible leads to a simpler feature and more choices for 
users.

#### A road not taken

We choose to divide the world of string literals first into raw and 
non-raw literals; from this, multi-line strings falls out for free as we 
can treat line breaks in the source file as just more raw characters.

We could have chosen, instead, to first divide the world into single and 
multi-line strings, and then into raw and non-raw; this would have left 
us with four choices (raw single line, raw multi-line, cooked 
single-line, cooked multi-line.)  This also would have been a defensible 
position, but seemed to add lexical complexity for little gain.

#### The exception that proves the rule

The one exception to raw-ness is that we normalize the line terminators 
to the most common (*nix) choice of a single newline, rather than using 
the platform-specific line terminator on the system that happens to have 
compiled the classfile.  The alternative would have just been too 
surprising.


## Syntax

Given that this feature has such a high syntax-to-substance ratio, we 
should expect more than the usual number of syntax opinions. Let's start 
with some consequences of our chosen design center.

#### No fixed delimiter

 From the design choice above, it is a forced move to accept variable 
delimiters.  Otherwise, one cannot represent a string with the delimiter 
in a raw string, without inventing an escaping mechanism, and subverting 
our "raw means raw" goal.

The "self-embedding test" is not a mere theoretical goal.  Since the 
snippets we expect to paste into Java source are not randomly chosen 
strings of characters, but meaningful snippets of some language, the 
likelihood of wanting to represent a string that contains the chosen 
delimiter goes up.  Even if you are willing to dismiss "embed Java in 
Java" as a serious use case (we're not), people also want a familiar 
delimiter, which means something that looks like the delimiter in other 
languages, further increasing the chance of collision.  (For example, if 
we'd picked a fixed triple quote delimiter, then you couldn't embed 
Groovy or Python code, among others -- surely a real use case).  Fixed 
delimiters (of any length) and "raw means raw" are not compatible goals, 
and we choose "raw means raw".

The credible options for variable delimiters are using a repeating 
delimiter sequence (say, any number of ticks), or some sort of 
user-provided nonce ("here" docs), or both.  Nonces impose a higher 
congnitive load on readers, and their benefit accrues mostly to corner 
cases, so the more constrained option of repeating delimiters seems 
preferable.

#### Why not 'just' use triple quotes

People's syntax preferences are guided by familiarity, so we should 
expect suggestions to be biased towards what "similar" languages already 
do.  So the suggestion of using """triple quotes""" should be expected.

We've already discussed how a fixed delimiter is not acceptable. So at a 
minimum, this would have to be adjusted to "three or more."  While some 
people find triple quotes natural (or at least familiar), others find it 
offensively heavyweight.  Neither crowd is going to convince the other.

#### But ticks are too light

The opposite of the "triple quotes are too heavy" argument is "ticks are 
too light"; that a single tick is a lightweight character, and could go 
unnoticed, especially if your monitor hasn't been cleaned for a while.  
Unfortunately the quote-like delimiters in the middle of the weight 
range are taken by other activities.  Again, we can't satisfy the "too 
light" and "too heavy" crowd at the same time; whichever we do will make 
some people unhappy.

#### Why do you have to always do something new?

The quoting scheme chosen -- any number of ticks -- is actually taken 
from something we all use: Markdown 
(https://daringfireball.net/projects/markdown/syntax), which permits any 
number of ticks to be used for infix sequences, and any different number 
of ticks to be embedded.  (Where we depart from Markdown is that 
Markdown strips any leading and trailing newlines from multi-line tick 
blocks, an appropriate trick for a page presentation language, but not 
consistent with the design goal of "raw".)

#### But I want indentation stripping

When embedding a snippet of one language in another, both of which 
support indentation, we are left with two choices: indent the enclosed 
block exactly, which has the effect of the code "jutting out to the 
left", or indent the enclosed block relative to the enclosing block, 
which has the effect of having more indentation than you might want for 
the enclosed block.  Sometimes this doesn't matter, but sometimes it 
does. Whatever we do, one of these crowds will be unhappy.  When in 
doubt, we stick to the principle of "raw means raw", and provide 
indentation stripping via new instance methods on `String` to allow a 
range of trimming options, such as `trimIndent()`.

#### But I want leading / trailing empty lines

Some people would like for the language to strip off leading and 
trailing blank lines.  Like indentation stripping, this is going to be 
what people want sometimes, and sometimes not.  And given that again, we 
can't do both, we again, are guided by "raw means raw", and provide 
library means to strip the extraneous newlines.

#### But I want a marker character to make it obvious

Some people would like a margin marker character, so they can manage 
margins like this:

     foo(`This is a long string
         >the characters up to, and
         >including, the bracket are stripped
         >by the compiler
         >    and this line is indented`)

(Others would argue the marker character should be "|".)  Again, we 
believe these sorts of transforms are the purview of libraries, not 
language, and will be provided.

#### But people will make ASCII art

     ``````````````````
     `Yes, they might.`
     ``````````````````

#### But I want to use unicode escaping

There will be library support for explicitly processing Unicode escape 
sequences, or backslash escape sequences, or both.

#### But calling library methods like `longString`.trim() is ugly

You say ugly; I say simple and transparent.

#### But doing these things in libraries has to be slower and yield more 
bloated bytecode

No, it doesn't.

## Anomalies and puzzlers

While the proposed scheme is lexically very simple, it does have some at 
least one surprising consequence, as well as at least one restriction:
  - The empty string cannot be represented by a raw string literal 
(because two consecutive ticks will be interpreted as a double-tick 
delimiter, not a starting and ending delimiter);
  - String containing line delimiters other than \n cannot be 
represented directly by a raw string literal.

The latter anomaly is true for any scheme that is free of embedding 
anomalies (escaping) and that normalizes newlines.  If we chose to not 
normalize newlines, we'd arguably have a worse anomaly, which is that 
the carriage control of a raw string depends on the platform you 
compiled it on.

The empty-string anomaly is scary at first, but, in my opinion, is much 
less of a concern than the initial surprise makes it appear. Once you 
learn it, you won't forget it -- and IDEs and compilers will provide 
feedback that help you learn it.  It is also easily avoided: use 
traditional string literals unless you have a specific need for 
raw-ness.  There already is a perfectly valid way to denote the empty 
string.

#### Can't these be fixed?

These anomalies can be moved around by tweaking the rules, but the 
result is going to be more complicated rules and the same number (or 
more) of anomalies, just in different places -- and sometimes in worse 
places.  While there is room to subjectively differ on which anomalies 
are worse than others, we believe that the simplicity of this scheme, 
and its freedom from embedding anomalies, makes it the winner.

Because we start with such a simple rule (any number of consecutive 
ticks), pretty much any tweak is going to be complexity-increasing.  It 
seems a poor tradeoff to make the feature more complex and less 
convenient for everyone, just to cater to empty strings.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20180327/4f60666f/attachment-0001.html>


More information about the amber-spec-experts mailing list