Matheus Moreira (blog) 直接在 Linux 系統呼叫上建構了 lone lisp。在這次專訪中,他分享了自己對 C 與 Linux 核心的見解。
你最初是如何接觸運算領域的?在發現 Lisp 之前,你走過怎樣的路?
我一直都很喜歡電腦,但真正讓我踏入程式設計的是電玩。當我還是個孩子時,我玩過一個叫做 Mega Man Battle Network 的遊戲系列,結果我從那些遊戲中獲得了靈感。它們最終讓我學會了英文;我曾以非英語母語者的身份加入論壇,嘗試與人交流。起初很艱難,但我進步了!我的第一門語言 C++ 也是如此。
你開始學 C++ 時多大?
在巴西有「技術學校」,也就是我曾就讀的那種。它們有一般高中課程,還有額外的專業課程。我不確定這在國外是否常見,也不知道有沒有專門的術語。還有其他課程,例如化學、機電……至於品質,雖然是基礎程度,但對高中生來說已經算不錯了。它確實促使我開始自學;在六個月內,我已經把整個課程學完了。最後他們還聘請我擔任助教,幫助其他同學。
因此,我在高中開始時,大約 13-14 歲,選修了資訊課程,並開始用一個叫做 Dev-C++ 的老舊 IDE 學習 C++,它現在還在 SourceForge 上!我也會瀏覽 cplusplus.com 的教學。我試著做遊戲,但成效不彰!那真的很難,尤其是因為我還沒學會要模擬的物理學 :) 最後我學會了更多語言。在具備基本的 C++ 能力後,我又學了不少 Java,接著是 Ruby、Python……最後才接觸到 Lisp 與 Scheme!我記得第一次看到 Scheme 時,覺得它極其優雅。 不過 Ruby 是我的最愛語言。那時候我寫了一些 gem。我想其中一個還有一點小跟隨者:Acclaim,一個類似 git 的命令列參數解析器。
很酷!你很開心地跳進新語言,還是先試著留在 C++?
我想我開始學更多語言,是因為我已經把 cplusplus.com 的教學都看完了。我覺得如果那個網站已經沒有東西可以學,那我應該繼續前進吧?Sun 以前有一個很大的 Java Tutorials 網站。我想我把那整個網站都讀完了。那是我學到物件導向程式設計的時候。我在青少年時期寫了一些 Java 程式……一些「工具函式庫」。回想起來,那只是 Apache Commons 1% 功能,而且實作得很糟糕!我甚至把它發佈出去,可能現在還在某處……我還記得用 Java Swing 做了很多實驗。我很喜歡 Nimbus 的外觀與感覺,還為我的數學老師做過一個畫圓/弧的應用程式。
是什麼促使你一直深入探索?是什麼激勵你不斷嘗試不同的語言?Ruby 你也持續使用嗎?
在求學初期,學校專案是很大的動力,但到了某個階段,主要是出於好奇心,以及對「正確的事物」的追求。我想要一種能稱之為家的語言。我跟 Ruby 相處最久。它真的是一門很棒的語言。我一直都有安裝它,最近還開始了一個 Rails 專案!但它也有缺點。我持續使用的另一門語言是 C。它低階的簡潔性真的很吸引我。
我沒有忘記 C++,但也沒有更新我的知識。目前的 C++ 基本上已經是一門全新的語言,所以我不再宣稱自己懂 C++。某種程度上,我用 C 取代了它。C 總是因為某種原因吸引著我,儘管它有各種問題與歷史包袱,但當我開始探索 CRuby VM 原始碼時,它才真正在我腦海中扎根。那時候我有個很有趣的消遣,就是留意 StackOverflow 上有人問「為什麼 Ruby 的行為像 X?」然後我會深入原始碼找出答案。
幾年前我試著轉向 Rust,但並沒有成功。我也是 Zig 及其創作者的忠實粉絲。從我觀看「The Road to Zig 1.0」主題演講,他說明人們「用 C 重寫東西」的原因:速度、ABI……我就一直很欣賞。
是什麼優缺點讓你留在 C 與 Ruby?
Ruby 是一門極具表達力的語言,它「剛好符合」我的心智模型。我常常可以猜到方法名稱,而且真的有效。它有單複數形式、同義詞等好處。我們的大腦就是用這些詞彙運作,而 Ruby 全部都支援。
Matklad 提到好的 API 應該可以被猜到,如果他猜到的東西不存在,他通常會提交 bug 報告,說這個 API 應該要有!
Ruby 還能讓人組成非常像 shell 的處理管線……items.each.with_index.map……它可以不斷組合,而且用很少的字元就能完成大量工作。
至於 C,主要的優點是輸出結果的簡潔性。你寫一個函式,它會編譯成 ELF 符號以及對應的組合語言程式碼。他們說你寫 C 的時候可以看到編譯器輸出,這是真的。用 C++ 的話,編譯器會加入很多機制,很難理解它們如何組合在一起。至於動態語言也一樣,但我發現探索虛擬機器的原始碼比探索編譯器的原始碼容易。
所以你的 C++ 風格改變了,把語法糖移除,直到你寫出 C 為止?
是的。我的風格也隨著我閱讀更多其他開發者寫的程式碼而改變。我探索過 CRuby、CPython、JVM,甚至一點 Guile 與 V8 的程式碼……不過當我開始閱讀 Linux 核心原始碼時,我才真正開始崇拜專業的 C 程式設計師。它真的很棒,大概是黃金標準。
從一開始你就讀了很多程式碼嗎?這在今天特別罕見,尤其是人們真的會去閱讀他人的程式碼。你是如何閱讀的?有什麼技巧或方法嗎?
我讀了很多。我一直都是那種好奇想知道東西如何運作的人。我不滿足於只是使用某個函式庫,我想知道它在底層做了什麼。我想知道驅動程式如何與硬體溝通。我想知道所有隱藏的有趣細節。那就是我感興趣的事情。我試著做的真正應用程式,最後總是會跑去探索這些不相干的東西。其他程式設計師會告訴我「為什麼要在那上面浪費時間?那已經被解決了」,但我想知道它是怎麼做的,最後我也開始產生一種傲慢,希望能把輪子重做,而且做得更好。
不過我還是聽進去了。我覺得自己因為這種怪癖,不適合軟體開發這條職業生涯,所以最後沒有走那條路。直到今天,我還是把它當作我的興趣。
你第一次看驅動程式碼時,是怎麼理解的?
一開始我完全看不懂……那很令人沮喪,但我一直堅持,最後終於能理解《一些》東西了。我當然不是硬體工程師……但我成功逆向工程了我筆電的一些功能。那仍然是我最自豪的專案之一。我的筆電是 Clevo 的裸機機種。它有一個很不錯的鍵盤,上面有 RGB LED,大多數都可以獨立控制。有一個非常糟糕的 Windows 應用程式可以控制那些 LED。我發誓它要花超過一分鐘才會顯示視窗,真的很惱人。我用 Wireshark 攔截它傳送給鍵盤的 USB 封包,然後為它寫了一個 Linux 使用者空間驅動程式。我擷取封包,盯著畫面看,直到搞懂它們的結構,然後寫了一個小小的 C 程式來幫我設定鍵盤。我把它丟到 GitHub 就忘了……有一天我回去看,發現有人在使用,甚至有人為它做了 GUI。那感覺真的很棒。我從試著逆向工程筆電其他功能中,學到了更多關於驅動程式與電腦技術的知識。ACPI 電源管理之類的東西。不過我並沒有完全搞懂,直到今天都還沒。
你有一篇關於 Linux 系統呼叫的文章,那是你走上 lone 這條路的契機嗎?
系統呼叫的程式碼非常簡潔,它們的運作方式就像一般的函式呼叫。我會說有趣的地方在於,它讓你可以繞過 libc,直接針對 Linux。Linux 以使用者空間 ABI 的穩定性聞名;使用者空間的元件則不然。它讓人們可以直接依賴 Linux 核心,而不需要其他中間層。
我一開始就有種感覺:人們不希望我使用它們。我會看手冊,他們會說「這個系統呼叫不被 glibc 支援」。我會看維基百科,上面會有一張資訊圖,顯示 glibc 包裝 Linux,好像它是 Linux 的 CRT,而其他 libc 不存在。後來我看到 LWN 上的一篇文章,說 getrandom 系統呼叫花了兩年才出現在 glibc 上。
CRT?
C 執行時期。標準 C 函式庫:glibc、musl、uclibc。
就是那個每當它們破壞相容性時,會讓整個世界崩壞的套件。因為所有東西都依賴它。我記得那篇文章提到「也許 Linux 應該有一個 liblinux.a,只包含系統呼叫,這樣當 libc 沒有幫助時,人們就可以直接繞過它,只要傳 -llinux 給編譯器即可」。我覺得那是一個很酷的專案想法,所以我去建立了 liblinux,就是那樣的東西:一個用於獨立式 C 程式設計的 Linux 系統呼叫包裝器集合。結果發現,針對 Linux 系統呼叫介面進行程式設計真的很有趣。C 的許多問題在獨立模式下就消失了。
在獨立模式下,C 的哪些問題會消失?
最基本的一個:errno。C 有「目前錯誤」的概念,這是一個全域變數。每個函式都會設定它,你應該去檢查它。Linux 只是像一般函式一樣把錯誤值回傳給你。C 函式庫會把那個值放進全域變數。這就是 C 的運作方式。但在獨立式 C 中不是這樣。C 的歷史包袱也消失了。C 函式庫裡有很多歷史遺留的程式碼。例如 locale 相關的東西,問題非常多。它也是全域狀態(一個反覆出現的主題!),可能會根據環境變數之類的東西造成非常微妙的錯誤。Liblinux 讓我學會了獨立式 C,以及 C 可以多乾淨……如果我們能刪除它所有的歷史包袱就好了。那很痛苦。我必須自己寫所有的函式。從基本的位元組複製函式,到整數轉字串的函式……不過問題並沒有我想像的那麼難以克服,我還是堅持下來了。我知道有疑問時,我永遠可以去讀程式碼。我可以看到前輩們是如何做的。glibc 儲存庫很難解讀,但 musl libc 的原始碼真的很乾淨,很容易深入研究。它非常有價值!它也激發了一些創意。例如,我如何在沒有 C 函式庫、沒有記憶體配置、只有靜態記憶體的情況下建立自動化測試套件?我成功在 lone 裡做到了!
我記得我甚至在 Reddit 上問過 Greg Kroah-Hartman,為什麼 Linux 從來沒有一個「官方」的 C 函式庫。他回覆說 Linux 曾經有一個 klibc 專案!不過 liblinux 的歷史到此為止:某個時候,我發現 Linux 核心開發者正在做完全相同的事情!他們建立了一個 nolibc.h 檔案,在自己的工具中內部使用。我覺得它比我自己寫的還要好,也更容易使用。所以我把那個專案封存,然後開始做另一件事:Lone Lisp。一個用那種典範寫成的完整程式語言。說到底,我只是想向自己證明,我認為這是可行的想法並不是瘋了。如果我能做出一個直接在 Linux 上執行的獨立式 Lisp,那就證明任何程式都可以用那種方式撰寫。
現代的 Linux 使用者空間既龐大又像迷宮。Linux 本身並非如此。我發現它其實是一個很乾淨的系統。一切的基礎其實是 Linux 系統呼叫二進位介面。我們可以把整個使用者空間都丟掉,只用 Linux 從頭開始。我們甚至可以把無所不在的 GNU 東西丟掉,如果我們願意的話。
為什麼我們不能有一個內建系統呼叫支援的編譯器?只要加入一個 system_call 關鍵字,用提供的參數內嵌 Linux 系統呼叫程式碼。不需要 libc 的廢話在中間。不需要 C 或任何特定語言。有人今天就可以做出一個語言,而那單一功能就會讓它在系統程式設計上和 C 一樣有能力。他們可以撰寫軟體,然後直接引導 Linux 進入那個軟體。- Matheus 2021
總結來說,獨立式 C 移除了一些有趣的 C 缺陷:所有全域狀態:errno、rand/srand 狀態、locale 狀態、strtok(不是 reentrant!)、偶爾會咬人的隱式檔案緩衝;設計不良的 API 如 gets(站不住腳)、atexit、signal,甚至 malloc;lone 的配置器好多了。math.h 遇到 NaN 會變得很亂,而當 errno 也牽涉進來時會更糟。有些東西永遠不會離開。C 那些神秘的整數提升規則依然存在。還有缺乏溢位檢查,不過我會用編譯器內建函式來處理。我也很擔心自己可能會引入自己的缺陷……傲慢遲早會反咬我一口。
你知道為什麼 nolibc.h 的做法沒有更流行嗎?
目前 nolibc.h 已經變成核心樹中一個不斷擴張的目錄,它已經變成某種臨時的 libc 了。不過他們似乎把它當作內部專案,用來編譯 Linux 自己使用的內部工具。我想 Linux 核心開發之外很少有人知道它存在。人們當然可以使用它來建構自己的專案,但我不知道 Linux 在這方面提供什麼樣的穩定性保證。
相關的是,我曾經送過 Cosmopolitan 一些 patch,例如幫助實作浮點數解析。我是這個專案與作者的粉絲。即使那些說它是「愚蠢」黑客技術的人,我認為他們也承認那其實是天才級的黑客技術。這是一個很棒的專案,它把大量非常巧妙的知識凝聚成單一形式,也就是 APE 格式。能解析成各種不同可執行檔格式的有效可執行檔,這件事本身就很奇妙。而且它還在上面做了很棒的事,支援在可執行檔中嵌入檔案。
至少思考傲慢以及如何避免缺陷,這很重要,希望如此。許多人從不內省……浮點數是 lone 唯一未檢查的功能,之後呢?你想在其中建構特定東西嗎。
就像 Steve Yegge 說的,單單建立一個直譯器就會創造出一個嬰兒,一輩子的工作。浮點數會很難,但前面的工作真的是無限的。
就在這個週末,我實作了錯誤處理系統。它是 Common Lisp 風格的可重啟條件系統。在此之前,每條錯誤路徑都是從直譯器硬性 exit(255)。我下一個任務是把所有那些呼叫點替換成可以在 Lisp 中處理的例外。我也剛剛推送了正確的尾呼叫最佳化支援……浮點數會是一個困難的功能,如果我送給 Cosmopolitan 的 patch 有任何參考價值的話。這是我必須實作的東西。我也希望最終能開始實作來自 Erlang/BEAM 系統的想法。我把結構設計成有一天可以有多個直譯器平行執行 Lisp 程式碼。那些都是語言層級的功能。
用這個語言本身來建構什麼?我想用 lone 做的第一個專案……是替換我的靜態網站產生器。我用我自己維護的 pugjs 分支來撰寫我的網站。我的長期目標是用 lone Lisp 寫我自己的系統來取代它。之後,我想寫我自己的 shell。我自己的工具程式。用我的方式重新打造傳統的 Linux 使用者空間。
那些都是我在開發 lone 時腦海中浮現的想法。我的一篇文章甚至提到了一個 env 的實作,它只是把環境傾印成 lone Lisp 的表格。那時候 lone 還沒有任何迭代功能,所以那是我能做的最好的事。現在 lone 有產生器了!我可以做更多……當我實作某個東西,它就能解鎖很多其他功能,這感覺真的很棒。嵌入式區段功能是我專門為那種「Linux 工具程式」使用情境而設計的。lone 直譯器有一個功能,它會讀取一個特殊的 ELF 區段,然後直接執行那裡的程式碼,就好像它被編譯進去一樣。所以我可以透過複製直譯器、在複製品中加入一些 Lisp 程式碼,來做出 lone 程式,它就會變成一個獨立的程式。它現在有了產生器、定界接續,而我剛剛引入了錯誤處理。
新的錯誤系統和 Common Lisp 的有多相似?
錯誤系統在許多方面都很相似,但沒有 Common Lisp API 那種由發出訊號的程式碼提供重啟方式的設計,例如 use-value。我的系統直接建立在定界接續的工作之上。在實作它們之前,接續只是 Scheme 的神秘功能之一,但我發現大家其實已經在使用它的某個版本。例外機制本質上是一種接續的形式,只是它們不會被捕捉成一個值。而神奇的地方就在於捕捉接續。Alexis King 關於定界接續的演講為我解開了謎團,它展示了最後只是 memcpy 堆疊。製作堆疊的副本,然後在其他地方「重播」那個堆疊,就像 VCR 一樣。可恢復的例外。錯誤發出訊號的系統最後就是這樣,上面還有一個處理程式機制。我必須實作「catch ErrorType -> ErrorType 的程式碼」這部分。我選擇了一個簡單的系統,就是把一個符號例如 'not-found,對應到一個 lambda。所以它會測試 lambda 的參數數量,如果它接受一個參數:(lambda (error) ...),它的運作方式就和所有其他語言一樣。但如果它接受兩個參數:(lambda (error continuation) ...),那麼它就提供了一種透過接續恢復程式碼的方式。
Lone 的錯誤系統讓 throw 回傳一個值給呼叫的程式碼。每個例外處理程式都可以選擇性地接收一個定界接續。如果它們接收了,它們就可以呼叫它,透過回傳一個值來恢復發生錯誤的程式碼。
你有拒絕任何特定的系統或方法嗎?
我傾向於拒絕其他語言解決這些問題時常用的重量級方式。例如,我明確拒絕了 Common Lisp 風格的「重啟 API」,它讓程式碼可以列舉多種重啟方式。我希望所有事情都用求值器本身簡單的語言機制來完成,所以重啟的運作方式就好像 signal/throw/transfer 回傳了一個值。我也拒絕了求值器中的「特殊情況」,而傳統的 Lisp 實作會讓 eval 明確支援特殊形式,例如 if。在 lone 中,求值器沒有那些特殊情況。它支援 FEXPR,它們本質上是求值器的外掛程式。這讓某些事情變得比原本需要的更複雜,只是為了那種優雅(至少在我看來)。例如,尾呼叫最佳化對純 Lisp 函式有效,但對原語就不太有效。原語的 TCO 後來已經修復,但有一段時間是有 bug 的,歸根究柢是因為我把它們從 eval 中抽離出來的設計選擇。我必須為它們引入特殊的機制來進行尾回傳。
我一直試著想出用語言內部方式來做常見事情的方法,那些方式會利用求值器的機制。例如,我讓向量(陣列)與表格(字典)可以被呼叫。([1 2 3] 0) 會回傳 1。然後 ([1 2 3] 0 4) 會修改向量,使它變成 [4 2 3]。接續與產生器也可以直接被呼叫。有兩個原語:control 與 transfer。Control 就像 begin,但它支援接續。Transfer 會從程式碼中跳出,跳到最近的 control 區塊。它會回傳一個可呼叫的接續值,編碼了跳出時的程式狀態。所以恢復它就是 (continuation 'some-value)。(control (transfer 0) (lambda (value continuation) continuation)) 然後你可以透過呼叫 (continuation 'something-else) 讓那個 (transfer 0) 呼叫回傳其他值。它像函式一樣運作(相同的呼叫慣例),也許你可以在它上面實作 currying。
lone 實際上使用了多少個系統呼叫?
當 lone 啟動時,只有父程序發出一個 execve 系統呼叫。然後大約有十幾個 mmap 系統呼叫,用於內部記憶體管理。接著它從標準輸入讀取指令檔,執行它,然後結束。非常簡潔。在執行過程中,它可能也會在內部觸發更多 Linux 系統呼叫,mmap、munmap、mremap(!)。模組系統還會使用 openat 與 close。在 Linux 超過 300 個系統呼叫的龐大陣容中,lone 只使用了很小的一部分。它在 Lisp 層級提供了一個 system-call 原語。所以 Lisp 程式碼可以做任何事情。許多那些系統呼叫也需要核心資料結構。建立/操作那些 C 結構的支援也還在進行中。例如,ioctl 是一個重要的系統呼叫,它會需要那個結構支援工作。它可以讓你取得終端機的大小。
你認為那些東西現在離實現還有多遠?你有考慮擴充「路線圖」嗎?
老實說我不知道。我不是很擅長組織。不過我在 AI 的幫助下進步了很多。我一直把 Claude 當作某種經理在使用。這項工作是無限的,但語言本身已經接近完整的功能集。Claude 為我做的最有價值的事情是……程式碼審查與專案管理。我已經獨自程式設計超過 15 年。讓另一雙眼睛看我的專案,批評我的程式碼,找出問題與開發不足的領域,對我來說真的是改變世界。我經常把整個 lone 程式碼庫丟給 Claude,叫它嚴格指出所有問題,找出未來發展的方向。與 AI 討論想法也非常有用,它讓我能夠在多個情況下精煉我的想法。我改進了垃圾回收器與記憶體配置器,因為 AI 能夠教我更好很多的演算法。我仍然不信任它為我寫程式碼。我太在乎 lone 了,不會不去仔細檢查進入其中的每一行。親自撰寫程式碼對我來說通常也是有教育意義的。但我絕對有罪把我的 JavaScript 靜態網站產生器丟給 Claude,讓它幫我快速開發整個功能。
當我要求 Claude 審查整個程式碼庫時,它會找出問題,也會建議未來的方向,然後把它們編譯成文件。「使用者想要 X」,「使用者拒絕了方法 Y」,它在管理大量只存在於我腦海中的短暫上下文方面做得很好。它基本上是在自動為我管理待辦事項清單。我常常會說「我們現在不做這個,把它記下來以後再做」,然後 Claude 會把它儲存在專案的記憶中。例如,我計劃中的一個功能是支援 GCC/clang 編譯器 API。最基本的一個是堆疊保護函式。我有一個註記,裡面儲存了所有關於那些 API 的已知資訊。
還有什麼啟發了這個語言?你使用「現代」的 Lisp 語法,現在這似乎是普遍的。
Ruby 與 Scheme 啟發了我。我記得寫 Ruby 時感覺有多棒,也記得 Scheme 作為一門語言有多乾淨。我想把兩者混合,看看會得到什麼。也因為 Lisp 真的很容易解析,這幫了很大的忙。我手寫了解析器,突然就有了所有那些可以玩的巢狀列表,而那正是我真正感興趣的。不過直到我在求值器中實作 FEXPR 支援,我才真正理解 Lisp。函式直接接收列表本身,這讓我終於恍然大悟。
我認為 [1 2 3] 代表陣列、{k v} 代表字典非常直觀且很好。除了 Clojure 與 newLisp,Ruby、Python、JavaScript 也有那種語法。不採用那種語法可能會有問題!它會為熟悉流行動態語言的程式設計師增加很多不必要的摩擦。Common Lisp 的字面量也有點醜:#(1 2 3) 是一個列表,但因為有 # 所以又不太是?
對你來說,什麼是好的程式碼風格?
我的 C 風格是 Linux 核心風格的修改版。我傾向於緊密遵循它,但在某些情況下會打破自己的慣例。例如,他們真的很喜歡統一的程式碼,讓它在視覺上容易解析。我喜歡更多地對齊與裝飾程式碼。我的 Ruby 風格偏好少用標點符號。method 'arg' 而不是 method('arg')。這個想法是讓程式碼讀起來更像英文散文。設計良好的 API 也有這個特性:collection.each.with_index { |item, i| ... }。現在對 Lisp 風格發表意見可能還太早,但我已經對減少語法巢狀發展出強烈的偏好。簡單的例子:let。在 lone 中,let 的運作方式是:(let (a b c d ...) body...),而不是那個歷史悠久的 (let ((a b) (c d) ...) body...)。我這樣做的理由與函式應用的實作相呼應。((lambda (x y)) 10 20) -> x 10, y 20。使用 (x 10) 與 (y 20) 列表感覺很不自然,它們不是真的成對,它們在我腦中感覺更像一個有序序列:x 10 y 20。
效能如何?
有一個基準測試 lone 贏了:啟動速度。它可以啟動並完成程式,比 Ruby 載入 Rubygems 還快。臨時基準測試顯示 lone 大約比 Python 慢 10-100 倍……它們屬於不同的效能類別:lone 是列表層級的直譯器,就像 SICP 第 5.4 章。Python 與 Ruby 是位元組碼直譯器。我勇敢地抗拒位元組碼編譯,只是因為我覺得如果我把列表轉換成位元組碼,我就會失去 Lisp 的精髓。如果求值器不是在處理列表,那還算是列表處理嗎?
我記得讀過一篇文章,說 CPU 的分支預測器變得如此精密,即使是普通的樸素直譯器也正在變快,它們現在似乎可以透過間接分支進行預測。這給了我一些希望!我長期的效能計劃是保留 C 直譯器作為最終的基準線,用來引導,然後在 lone 內部寫一個 JIT 編譯器。我可以 mmap 一些可執行的頁面,然後發出指令。那時候編譯就會開始……Python 經常因為效能不佳而被批評,所以我擔心 lone 會被欺負得更厲害。但事實是……它對使用者空間工作來說應該會出奇地可行。我對 bash 進行了一些臨時基準測試,然後徹底打敗了它,而人們一直在使用 bash!(不過那可能是測試的假象,因為遞迴費波那契數列並不是 bash 的強項,它可能在每次迭代時產生新程序……)但對於指令檔來說,啟動速度很重要。你不能有一個虛擬機器需要半秒鐘來引導,而 lone 在這方面真的很出色。
我也可以進一步改善 lone 的啟動速度。例如,Ruby VM 會執行相當多的程式碼來引導自己。它會做一些事情,例如在各處初始化雜湊表,例如把一些變數對應到 Ruby 物件的環境。對於 lone,我將不得不加入一個建置步驟來預先計算所有雜湊表,並將它們編譯成 C 初始化器!然後 Linux 會 mmap 可執行檔,它就準備好了,就這樣!對指令檔來說很棒!Zig 的 comptime 會讓這件事變得非常容易,但我開始做 lone 時並不太了解 Zig。
Lone 有一些「內建」模組;頂層環境只包含兩個符號:import 與 export。從那裡開始,所有模組都會引導它們的環境。即使是 if 與 quote 也必須被匯入,因為我希望使用者對環境中的每一個名稱都有完全的控制。除了列表、字面量與函式呼叫機制之外,你必須匯入《所有東西》。目前它非常斯巴達;我只實作了我自己實作所需的東西,例如 flatten。總共有大約 50 個函式,主要在 lone 模組中,裡面包含語言功能:if、let、begin、control/transfer、intercept/signal……
mmap 到底是如何運作的?
mmap 本質上是一個要求核心給你記憶體「頁面」的函式。它很像 malloc,但層級更低。每個頁面通常是 4096 位元組的記憶體,你可以要求它的倍數。你也可以要求那些頁面有特定的保護。可讀取的頁面、可寫入的頁面,以及可執行的頁面(這是 JIT 編譯器使用的)。記憶體配置器會在它上面建立更高層級的介面,因為當你只需要 16 位元組卻要求 4 KiB 時,會浪費很多記憶體。所以配置器會要求 128 MB,然後把它切成小塊來滿足程式的需求。
在測試中,你有各種範例,但目前還沒有語言文件(只有實作相關的東西)。到時候你會如何處理文件?
它非常不完整,但事實是我快要沒有東西可以加入語言了;我很快就要開始處理標準函式庫。我會從 Ruby 與 Scheme 尋找靈感。在我看來,Ruby 的標準函式庫是無與倫比的,同時我也喜歡 Scheme 的命名方式。迭代協定是下一個清單上的項目。它會像是 (for (each collection) (lambda (item) ...))。我已經有一些 each 函式,試著在那方面模仿 Ruby。它們應該要重新設計。它們都應該回傳產生器。然後是字串操作函式。甚至是一個基本的字串建構器,能夠有效率地串接字串,這對讓 lone 變得有用會有很大幫助。我打賭如果我加入這個,並完成 lone 的迭代故事,我就可以在 lone 中寫出 hiccup 風格的網頁框架。
不過當我到達那個階段時,我有可能會被 ADHD 困擾。現在我正在處理所有這些有趣的低階東西。一旦那個完成,變成「哦,現在我只需要做標準函式庫」,我可能會很難管理它……到時候我會讓 Claude 來鞭策我。
我認為 APL 與 Lisp 更簡單,我試著宣揚它們,賦權並展示它們有多容易取得……我很少成功。我試著用「哦,那只是 X Y Z」來讓人們驚嘆,好像那真的很簡單,但我得到的卻是相反的效果,它反而讓人感到害怕。
我只是喜歡擁有這些洞見,對吧?像是接續。哦,它只是複製堆疊框架而已。它以前是神奇的,現在變成一件普通的事情。我有種感覺人們並不喜歡這種洞見,因為他們根本不在乎。說到底,lone 的使命之一是激勵人們擁有自己的系統並進行探索。寫手冊的人告訴我我不能。你不能使用這些系統呼叫的東西,它不是給像你這樣的凡人的。我想使用被禁止的東西,我想成為製作函式庫的人,而不僅僅是使用它們。我希望 lone 能激勵其他人做同樣的事。我認為人們不這樣做的一個原因是責任。要擁有自己的系統,你必須擁有它。如果它壞了,你必須修復它。人們認為他們在生活中已經有太多責任,而電腦只會再增加一個。我希望更多人選擇那個責任,並享受它帶來的所有回報。
撰寫 lone 讓我擁有完全的控制權,甚至包括我周圍的名稱。感覺運算充滿了歷史遺留。所有這些根深柢固的程式碼,它們是錯的、醜的,但沒有人去碰它們,因為如果你刪除它們,就會破壞一切。我希望看到人們被鼓舞,開始刪除那些壞東西。也許重做 Linux 使用者空間的人不是我,而是 Rust 或 Zig 的人。我擺脫了 libc,他們警告我它會破壞東西,它會破壞 DNS,它會破壞一切。我本來可能會被嚇到,但我繼續前進。我只是想向世界展示,看,沒有什麼好害怕的。你可以剝去所有這些層級。它是安全的。而且也是一段有趣的旅程!即使 lone 沒有成為什麼,如果我至少能激勵那件事,我就會很開心。
我喜歡這種對整個堆疊採取激進所有權的訊息,因為你可以。這和 Decker 或 Fennel 及其遊戲有相同的激勵之火。
是的。我真的很喜歡自由軟體的使用者賦權價值觀。電腦是改變世界的技術,我們應該擁有它們,我們不能讓企業或政府控制它們。當我看到人們不關心所有權,讓公司為他們「管理」電腦時,我感到非常難過。因此,通用電腦變成了家電,變得更低階。很多人討厭 systemd 之類的專案,但某種程度上,他們正在做我想要激勵的完全相同的「重新想像」Linux 使用者空間。也許比我希望的更粗暴,但我無法否認 systemd 有多棒。他們重新發明了它,而且很棒。我想看看人們一旦脫離傳統的 *nix 系統,還會想出什麼其他東西。選擇自己的戰鬥也很重要。如果我試著把 lone 做成裸機 Lisp,我不確定它是否會成功啟動。我會讓比我聰明得多的人去做那件事。jart 的 sector lisp 是一個很好的例子……在 512 位元組的開機磁區程式碼中實作 Lisp。
我每天都使用 Termux 來自動化很多事情。如果你告訴你 lone 是在 Termux 裡誕生的,你會相信嗎?我寫了很多是在手機上。雖然 Android 封鎖了一些系統呼叫(statx 是咬到我的那個),但 lone 可以在 Android 上執行,而且測試套件 100% 通過!
你決定不走軟體職業生涯,你現在在做什麼?
我是醫生。我在 2019 年從醫學院畢業。一開始是 AI。在高中時,我開始喜歡生物,因為我在閱讀關於神經網路程式的東西。有些日子我會連續工作 10 到 14 小時,從事醫學工作。有些日子我完全不工作。今天就是這樣的一天。沒有預約。今天沒有看診。醫學提供了一點自由,讓人可以自己安排時間。有很多職業路徑可以選擇。外科醫生實際上住在醫院裡。我想要更輕鬆的生活方式,因為我需要時間放鬆。我在一家有電子病歷的熱門診所工作。它真的很有效率,尤其是對像我這樣的科技人來說。即使系統很慢或缺少功能,我也可以用自己的技能來彌補。即使是一些儲存在電腦上的小範本,也能讓速度加快 100 倍。因此我可以快速看診,並與病人維持良好的關係。即使是能夠盲打也有幫助!我可以在看著人們眼睛的同時輸入所有東西!我的同事做不到!我之前提到的 Rails 專案正是如此。我正在製作我自己的電子病歷系統來解決我自己的痛點。即使它失敗了,我也會得到很好的服務。
非專業程式設計師非常少。你當然很早就開始了,但為什麼你認為更多專業人士不去解決他們自己的痛點?
我認為很多人其實並不喜歡電腦。要改變這件事,我們必須讓他們對運算感興趣,電腦可以有多棒。學習確實需要努力,但大多數人的心態似乎都專注在結果上。我在高中時就已經在選擇參加程式設計課程的人身上看到那種態度。好奇心是必要的,要關心結果之外的事情,要深入了解香腸是如何製成的。我不知道如何激勵人們做到這一點。如果你找到了方法,我很想知道。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.