因为通宵看恋综让我差点误了 TC39 第100次会议的提案 deadline

9,984 阅读4分钟

这个恋爱综艺是挺上头,我连夜看了,但也得吐槽一下,上次我找 Hux黄玄 帮我一起搞 JS 提案他就说没空,原来是上恋综去了。我只好自己写提案。而且因为通宵看恋综,磕他和二号女嘉宾「毒舌凯西」的糖(不用搜了,这是本人给她起的诨号),我差点误了给 TC39 标准委员会第100次全体大会提交议题的 deadline(这次会议是2月6日,deadline 是会议前 10 天,也就是1月27日,这个恋综的播出时间是1月26日晚上)。

关于这次的提案,起因是这样滴: 当前 JavaScript 代码里要内嵌一段包含 backtick (`)的大段文本是不容易的。你得用转义符。比如

let sql = `
  select * 
  from \`table\` 
  where \`name\` = ?
`

【MySql 等数据库要求字段用`包裹,但是我们没法直接在 JS 里写,只能转义 \`。】

当然这个例子很简单,你可以用传统字符串就不用转义了,但复杂文本或者大段文本我们一般都用 template literal。而 template literal 使用`作为定界符,你就不能在文本里直接包含它。用 String.raw 也不解决问题甚至更糟,因为你压根没法在 String.raw 后的 template literal 里直接表达`(因为连转义都没了),你只能写成

let sql = `
  select * 
  from ${"`"}table${"`"} 
  where ${"`"}name${"`"} = ?
`

【没有 escaping 只能用插值】

我打赌没人想阅读这样的代码。

当然,各位程序员可以脑洞大开,用其他方式搞得好看一点,比如这样:

let sql = `
  select * 
  from 🤡table🤡 
  where 🤡name🤡 = ?
`.replaceAll("🤡", "`")

但这并不解决实质性问题 —— 比如如果大段文本是从别的地方 copy 过来的,你还是得先转换一下。

另一个比较常见的方式是把这些文本放在其他文件,然后程序里读取出来,不过这样也有代价:

  1. 这强制文本和源代码分离
  2. 没法直接使用插值了,如果有需求,就得用第三方的template引擎
  3. 读取文件的方式很难跨平台,比如浏览器和 Node.js 就不一样
  4. 读取文件需要额外的IO,当然可以使用打包器 inline 进来,但有类似第二点的问题

总之就是增加了非必要的复杂性

这个问题由来已久,但一直没人提到委员会去,这次我实在忍不住要自己出手,是因为我在写调用 GPT 的代码,JS 程序里直接写 prompt,prompt 的格式是 markdown(因为 GPT 对 markdown 格式理解得很好),markdown 里使用了 fenced code block(一个 fenced code block 就包含至少6个 backtick)来包含 JS 代码,JS 代码本身可能包含 template literal。可想而知如果都用转义这代码看起来有多悲剧。

let promptForLLM = `
You are a AI assistant to give advice to programmers,
for example, given the code:
\`\`\`js
let s1 = "This is a\\n"
  + "string across\\n"
  + "multiple lines.\\n"
let a = 1, b = 2
let s2 = "a + b = " + (a + b)
\`\`\`
you would output the advice:
\`\`\`\`markdown
## Advice
It's more readable to use template literal to replace
the string concatenation.

## Original code
\`\`\`js
let s1 = "This is a\\n"
  + "string across\\n"
  + "multiple lines.\\n"
let a = 1, b = 2
let s2 = "a + b = " + (a + b)
\`\`\`

## Improved code
\`\`\`js
let s1 = String.dedent\`
  This is a
  string across
  multiple lines
  \`
let a = 1, b = 2
let s2 = \`a + b = \${a + b}\`
\`\`\`
\`\`\`\`
`

【特别注意,因为没法用 String.raw,所以里面的 \n 要双重转义,写成 \\n。】

归根到底这个问题不引入新语法是没法解决的。所以只有写提案了:github.com/hax/proposa… 。(我得特别感谢 C# 团队,我大段文字都是从 C# 11 的类似特性文档里抄的。)

如果有 raw string literal,就清楚很多了(即使语法高亮不甚准确 —— 因为语法高亮器不认识这个假想中的语法):

let promptForLLM = @`````
    You are a AI assistant to give advice to programmers,
    for example, given the code:
    ```js
    let s1 = "This is a\n"
      + "string across\n"
      + "multiple lines.\n"
    let a = 1, b = 2
    let s2 = "a + b = " + (a + b)
    ```
    you would output the advice:
    ````markdown
    ## Advice
    It's more readable to use template literal to replace
    the string concatenation.

    ## Original code
    ```js
    let s1 = "This is a\n"
      + "string across\n"
      + "multiple lines.\n"
    let a = 1, b = 2
    let s2 = "a + b = " + (a + b)
    ```

    ## Improved code
    ```js
    let s1 = @``
      This is a
      string across
      multiple lines
      ``
    let a = 1, b = 2
    let s2 = `a + b = ${a + b}`
    ```
    ````
    `````

【特别注意,以上只是用于说明语法可行性,最终具体语法还需要调研,目前我只是先把提案的目标列清楚。】

那么其他编程语言有这个问题吗?我看下来,目前比较完美解决的是 C# 和 Swift,如果不考虑 interpolation,Rust、C++还有采用 heredoc 语法的各类 shell 和他们的朋友(perl、ruby、php)也算 ok。Python 不算完美,但触发问题的概率小一点。跟 JS 一样难兄难弟的还是要属 Golang 和 Java 系。希望这次我的提案能通过委员会,进入 stage 1,让 JS 有望先脱离这个坑吧。

最后感谢 Hux黄玄 让我蹭个流量。

【BTW,另外如果有人真是想看我对这个恋综的技术分析才点进来看,并且都读到这里了,也不要失望,我正在写。】