这个恋爱综艺是挺上头,我连夜看了,但也得吐槽一下,上次我找 Hux黄玄 帮我一起搞 JS 提案他就说没空,原来是上恋综去了。我只好自己写提案。而且因为通宵看恋综,磕他和二号女嘉宾「毒舌凯西」的糖(不用搜了,这是本人给她起的诨号),我差点误了给 TC39 标准委员会第100次全体大会提交议题的 deadline(这次会议是2月6日,deadline 是会议前 10 天,也就是1月27日,这个恋综的播出时间是1月26日晚上)。
关于这次的提案,起因是这样滴: 当前 JavaScript 代码里要内嵌一段包含 backtick (`````)的大段文本是不容易的。你得用转义符。比如
javascript
let sql = `
select *
from \`table\`
where \`name\` = ?
`
【MySql 等数据库要求字段用`````包裹,但是我们没法直接在 JS 里写,只能转义 `````。】
当然这个例子很简单,你可以用传统字符串就不用转义了,但复杂文本或者大段文本我们一般都用 template literal。而 template literal 使用作为定界符,你就不能在文本里直接包含它。用 `String.raw` 也不解决问题甚至更糟,因为你压根没法在 `String.raw` 后的 template literal 里直接表达
(因为连转义都没了),你只能写成
javascript
let sql = `
select *
from ${"`"}table${"`"}
where ${"`"}name${"`"} = ?
`
【没有 escaping 只能用插值】
我打赌没人想阅读这样的代码。
当然,各位程序员可以脑洞大开,用其他方式搞得好看一点,比如这样:
javascript
let sql = `
select *
from 🤡table🤡
where 🤡name🤡 = ?
`.replaceAll("🤡", "`")
但这并不解决实质性问题 ------ 比如如果大段文本是从别的地方 copy 过来的,你还是得先转换一下。
另一个比较常见的方式是把这些文本放在其他文件,然后程序里读取出来,不过这样也有代价:
- 这强制文本和源代码分离
- 没法直接使用插值了,如果有需求,就得用第三方的template引擎
- 读取文件的方式很难跨平台,比如浏览器和 Node.js 就不一样
- 读取文件需要额外的IO,当然可以使用打包器 inline 进来,但有类似第二点的问题
总之就是增加了非必要的复杂性。
这个问题由来已久,但一直没人提到委员会去,这次我实在忍不住要自己出手,是因为我在写调用 GPT 的代码,JS 程序里直接写 prompt,prompt 的格式是 markdown(因为 GPT 对 markdown 格式理解得很好),markdown 里使用了 fenced code block(一个 fenced code block 就包含至少6个 backtick)来包含 JS 代码,JS 代码本身可能包含 template literal。可想而知如果都用转义这代码看起来有多悲剧。
javascript
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,就清楚很多了(即使语法高亮不甚准确 ------ 因为语法高亮器不认识这个假想中的语法):
javascript
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,另外如果有人真是想看我对这个恋综的技术分析才点进来看,并且都读到这里了,也不要失望,我正在写。】