JavaScript 的分號,以及 ASI (Automatic Semicolon Insertion)

目前 community 的主流跟我理出來的期望不一樣... 所以記錄一下。

先提一下背景,在 JavaScript 程式語言裡面,在大多數的情境下是可以省略掉分號 (;) 的,也就是說這兩種寫法都是合法的 JavaScript 語法:

console.log('Hello, world.')
console.log('Hello, world.');

這是因為在 ECMA-262 裡面有 ASI (Automatic Semicolon Insertion) 的設計:「Automatic Semicolon Insertion」。

ASI 設計本身是好的,可以讓開發者少處理 ;,但偏偏 EMCA-262 又允許千變萬化的換行,於是就造成了各種奇怪的現象。

因此早期在 community 上都是推薦無條件加上分號,這可以避免各種奇怪的 bug 與 error,像是「Do you recommend using semicolons after every statement in JavaScript?」以及「Should I use semicolons in JavaScript?」這些問答。

大家會推薦加上分號主要的原因是因為,不加上分號遇到的 error 與 bug 不是那種你知道很雷,所以會主動查詢 & 避開的問題:

而是各種平常寫就會遇到的情況,最容易中獎的是第二組敘述是 ([ 開頭的,像是 ECMA-262 文件裡面提到的 case 就算常見了:

a = b + c
(d + e).print()

// 等價於:
a = b + c(d + e).print();

而且不因為註解受到影響:

// blahblah
a = b + c

// blahblah
(d + e).print()

// 還是等價於:
a = b + c(d + e).print();

另外一個常見的情況是我們會利用 anonymous function 包出一個 block,避免變數污染到外面:

// I want to do blahblahblah...
(() => {
  const a = '';
  // ...
})()

// I want to do another blahblahblah...
(() => {
  const a = '';
  // ...
})()

大多數的情況應該會 error,除非第一個 anonymous function 傳回一個 callable,而這種情況跑出來的結果就更慘了...

另外這種 case 也是常見的情況:

// ...
[1, 2, 3].forEach(...)

// ...
[4, 5, 6].forEach(...)

// 等價於:
[1, 2, 3].forEach(...)[4, 5, 6].forEach(...)

這邊省略分號最大的問題是你無法知道「自己這行需不需要加上分號」,因為註解可能很長有個 30 行,所以依照這些現況,比較好的方法應該是全部加上分號,保持一致性。

但這幾年所有的 frontend framework 都是推動拿掉分號,這可以從各家的文件看到,就搞不懂 community 是怎麼推導出來的... 在全部都拿掉分號的情況下,遇到上面的情況就得寫成不一致的 style:

// ...
[1, 2, 3].forEach(...);

// ...
[4, 5, 6].forEach(...)

查了目前可行的 workaround,大多都是透過 ESLint 類的工具來擋可能會出現 bug 的地方,也只能先這樣做了...

InfluxDB 好像又在搞事了:從 Golang 換 Rust

在「Influxdb made the switch from Go to Rust (reddit.com)」這邊看到 RedditInfluxDB 的 CTO 出來的解釋:「influxdb officially made the switch from Go => Rust」。

可以看到 Hacker News 上的討論很多人都有提到 InfluxDB 的各種問題,而且在量還不大的時候就會遇到了。

這次 Golang 換成 Rust,依照 InfluxDB CTO 的說法有這些「優點」:

  • No garbage collector
  • Fearless concurrency (thanks Rust compiler)
  • Performance
  • Error handling
  • Crates

不過如果在 Golang 沒辦法解決 scalability 的問題 (通常需要 profiling 找出熱點然後改善演算法),Rust 這邊遇到一樣的問題應該也是一樣炸裂...

另外這家公司先前也出過事,七月的時候 InfluxDB 把比利時區的服務給關掉,但有不少客戶因為種種原因沒有收到通知,加上他們是直接 hard shutdown (沒有備份資料),造成 InfluxDB Cloud 的企業用戶直接幹翻天:「InfluxDB Cloud shuts down in Belgium; some weren't notified before data deletion (influxdata.com)」。

基本上可以閃遠一點... 目前看到的替代方案有 TimescaleDB (在 comment 裡有看到一些抱怨) 與 Clickhouse (在這篇的 comment 討論的比較少)。

Perl 5.38 引入的 class

Hacker News 首頁上看到「Perl 5.38 (perl.org)」這個,從討論裡面可以看到比較大的改變是支援了 class 語法,對應的文件在 perlclass 這邊:

This document describes the syntax of the Perl's class feature, which provides native keywords supporting object-oriented programming paradigm.

傳統上的作法是用 bless,對一個 reference 上面綁定 class,也因為這是很底層的實作,所以社群就有很多物件的封裝,像是古董的 Class::Accessor,或是後來有在 Perl community 流行一陣子的 Moose

目前的 class 看起來像是 syntactic sugar?加上 Perl community 的能量沒有多高,長時間混用的情況應該是跑不掉...

Node.js 20

看到 Node.js 推出 20 了,官方的公告:「Node.js 20 is now available!」。

裡面提到的 Permission Model,設計上看起來有點雷?這種東西應該要有白名單機制才對,目前看起來是實做黑名單機制...

然後結尾有提到 14 是這個月收攤,16 則是因為 OpenSSL 1.1.1 EoL,打算切齊而提前到今年九月收 (參考 OpenSSL 官方前陣子發的「OpenSSL 1.1.1 End of Life」):

Also of note is that Node.js 14 will go End-of-Life in April 2023, so we advise you to start planning to upgrade to Node.js 18 (LTS) or Node.js 20 (soon to be LTS).

Please, consider that Node.js 16 (LTS) will go End-of-Life in September 2023, which was brought forward from April 2024 to coincide with the end of support of OpenSSL 1.1.1.

查了 18 會是 2025 年四月底,20 則會是 2026 年四月底...

前陣子 Hacker News 很慢的一些背景知識

看到 Ask HN: Is Hacker News slow for anyone else? 這邊的討論,dang (Hacker News 的管理員) 在 35157344 這邊就有出來說明:

All: our poor server is smoking today* so I've had to reduce the page size of comments. There are 1500+ comments in this thread but if you want to read more than a few dozen you'll need to page through them by clicking the More link at the bottom. I apologize!

Also, if you're cool with read-only access, just log out (edit: or use an incognito tab) and all will be fast again.

* yes, HN still runs on one core, at least the part that serves logged-in requests, and yes this will all get better someday...it kills me that this isn't done yet but one day you will all see

另外比較特別的是,Hacker News 是用 Arc (Lisp) 寫的,不過看起來沒有考慮到 optimization,加上那天 Reddit 也掛了,的確帶動 Hacker News 這邊更新的頻率比較高...

Ruby 再引入另外一套 JIT 實做:RJIT

Hacker News Daily 上看到「RJIT #7448」這個,Ruby 上一套新的 JIT 實做。

這次的 RJIT 取代掉先前的 MJIT:

This PR replaces the current implementation of MJIT with a new JIT called "RJIT"

有些特點,其中一個是 RJIT 在 buildtime 與 runtime 都不需要 compiler,這是因為 RJIT 直接用 Ruby 實做:

RJIT uses a pure-Ruby assembler to generate native code

  • MJIT requires a C compiler at runtime. YJIT requires a Rust compiler at build time. RJIT doesn't require them.
  • This means that RJIT's warmup could be slower than YJIT, but it's still much faster than MJIT's.

另外值得注意的是,RJIT 的作者 k0kubunYJIT 的作者 Maxime Chevalier-Boisvert 都是 Shopify 的員工,可以看出 Shopify 對於 Ruby 效能的痛?決定直接自己養人改善效能。

回到 RJIT 這邊跑的測試,可以看到他是用 YJIT 的測試套件測,這也就不會太奇怪了。

跟這次取代掉的 MJIT 相比,RJIT 在 Headline 這包測試都 OK,在 Other 這包則是有來有回,而在 Micro 這包則是有不少項目輸掉 (相比於前兩者):

這樣整體看起來算是有進步,下一版 Ruby 更新應該就會有了。

Ruby 3.2.0 把 YJIT 列為穩定功能了

去年有寫過 RubyYJIT 帶來的效能提昇:「YJIT 帶給 Ruby 大量的效能提昇」。

在這次的 Ruby 3.2.0 發布就把 YJIT 列為穩定功能了:「Ruby 3.2.0」。

  • YJIT is no longer experimental
    • Has been tested on production workloads for over a year and proven to be quite stable.

另外就是支援的平台,看起來是多了 arm64 這邊的支援,所以馬上列表就多了一堆新機器:

  • YJIT now supports both x86-64 and arm64/aarch64 CPUs on Linux, MacOS, BSD and other UNIX platforms.
    • This release brings support for Apple M1/M2, AWS Graviton, Raspberry Pi 4 and more.

另外是每個程式語言幾乎都會遇到的 regexp 類的問題,這次 Ruby 3.2.0 利用 Memoization 的方式降低某些 regexp 的消耗:

# This match takes 10 sec. in Ruby 3.1, and 0.003 sec. in Ruby 3.2
/^a*b?a*$/ =~ "a" * 50000 + "x"

而另外一組 regexp 也可以看出類似的效果:

用一些記憶體空間換取效能,降低被 DoS 的一些機會。另外一方面,引入了 regexp timeout 的 workaround,緩解真的被打的時候的資源消耗上限:

The optimization above cannot be applied to some kind of regular expressions, such as those including advanced features (e.g., back-references or look-around), or with a huge fixed number of repetitions. As a fallback measure, a timeout feature for Regexp matches is also introduced.

C 語言裡面的 ??! 符號

Hacker News Daily 上看到這個奇怪的知識:「What does the ??!??! operator do in C? (stackoverflow.com)」,原文在 Stack Overflow 上:「What does the ??!??! operator do in C?」。

這是 trigraph,在 C89 就有了,從 Rationale for International Standard—Programming Languages—C 這邊的 5.2.1.1 可以看到 trigraph 的歷史原因:

Trigraph sequences were introduced in C89 as alternate spellings of some characters to allow the implementation of C in character sets which do not provide a sufficient number of non-alphabetic graphics

而且是強制要求實做:

Implementations are required to support these alternate spellings, even if the character set in use is ASCII, in order to allow transportation of code from systems which must use the trigraphs. AMD1 also added digraphs (see §6.4.6 and §MSE.4).

其中遇到的問題就是當年得決定 C 可以用的 charset,得考慮到很多不同機器 charset 相容性的問題:

The C89 Committee faced a serious problem in trying to define a character set for C. Not all of the character sets in general use have the right number of characters, nor do they support the graphical symbols that C users expect to see. For instance, many character sets for languages other than English resemble ASCII except that codes used for graphic characters in ASCII are instead used for alphabetic characters or diacritical marks. C relies upon a richer set of graphic characters than most other programming languages, so the representation of programs in character sets other than ASCII is a greater problem than for most other programming languages.

然後就使用了 ISO/IEC 646 這個標準 (要記得 Unicode 1.0.0 是 1991 年才出現):

The solution is an internationally agreed-upon repertoire in terms of which an international representation of C can be defined. ISO has defined such a standard, ISO/IEC 646, which describes an invariant subset of ASCII.

The characters in the ASCII repertoire used by C and absent from the ISO/IEC 646 invariant repertoire are:

[ ] { } \ | ~ ^

後面就是定義 ?? 當作 escape digraph。

算是一個歷史產物,現在不太需要用到了...

用 GPT-3 解讀程式碼

Hacker News 上看到的方法,Simon Willison 試著把程式碼餵進 GPT-3,然後問 GPT-3 程式碼的意思,看起來答的還不錯:「Using GPT-3 to explain how code works」,對應的討論 (包括 Simon Willison 的回應) 則可以在「Using GPT-3 to explain how code works (simonwillison.net)」這邊看到。

第一個範例裡面可以解讀 regular expression,雖然裡面對 (?xm) 的解讀是錯的,但我會說已經很強了...

第二個範例在解釋 Shadow DOM,看起來也解釋的很不錯...

第三個範例回來原來產生程式碼的例子,拿來生 SQL 指令。

後面的 bonus 題目居然是拿來解釋數學公式,他直接丟 TeX 文字進去要 GPT-3 解釋柯西不等式 (Cauchy–Schwarz inequality)。這樣我想到以前高微作業常常會有一堆證明題,好像可以丟進去要 GPT-3 給證明耶...

測試 Neovim + GitHub Copilot

如同之前在「GitHub Copilot 宣佈 GA」提到的,Copilot 有支援 Neovim,找了一下在 GitHub 上的 github/copilot.vim 這邊可以取得。

Copilot.vim is a Vim plugin for GitHub Copilot. For now, it requires Neovim 0.6 (for virtual lines support) and a Node.js installation.

主要有兩個 dependency 問題,第一個是 Neovim 版本要 0.6+,而在 Ubuntu 20.04 內的版本不夠新 (22.04 的看起來就夠),可以裝 PPA 版本解決:「Neovim Stable」。

另外一個是 Node.js 版本需要到 16+ (20.04 與 22.04 內建的都不夠),這個我是靠 nvm 解決。

先在 GitHub 網站上開通 Copilot,再照著說明,回到 Neovim 裡執行 :Copilot setup,跟著步驟跑授權流程就可以了。

接下來隨便開個 test.py 或是 test.php 檔開始寫,就會發現有 suggestion 跑出來了。

這邊拿 feedgen 測試會不會動,輸入 feed. 後就會出現灰色的 subtitle(title)

這時候按 tab 就會展出來了。