這篇文章最初發表於 David 的網站,經授權在此轉載
去年一月,Exercism 宣布了一個名為 12in23 的新計畫,挑戰參與者在 2023 年嘗試 12 種新的程式語言。每個月都有一個主題(例如「分析四月」或「物件導向十月」),並會特別介紹一些值得嘗試的程式語言。我熱愛學習新事物,也已經算是個(程式)語言狂熱者了,所以我決定試一試。12 種語言,12 個月!
如今這一年即將結束,我對這個計畫的成果感到非常滿意。我成功嘗試了 12 種新語言,在 Exercism 社群裡認識了很棒的人,過程中還貢獻了一些很酷的開源專案!在這篇文章裡,我會逐一回顧這些語言,並談談我從每一種語言中學到了什麼。
挑選程式語言
我為這一年訂下了幾條原則,好讓自己從這段經歷中獲得最多收穫:
- 語言必須是全新的,或至少讓我不夠熟悉到會覺得自己學到很多。
- 挑選的語言應該(可能)對未來繼續深入學習有實際用處。這個計畫純粹為了好玩,但我希望花時間學的東西(至少部分)是有用的。
- 我會為使用的每一種語言安裝所有本機工具和 VSCode 外掛。我想在盡可能對等的基礎上比較各種語言,並享有盡可能多的型別提示和 IntelliSense。大學時,我用 Sublime Text 寫所有的程式作業,沒有任何 lint 或自動補全。我當時擔心,如果我在學寫程式時用了這些工具,就會太依賴它們,無法成為優秀的程式設計師。結果恰恰相反。我愈能把認知負擔卸給工具,就愈能專注思考眼前的實際問題。不要記住東西,要記住怎麼找到它們。
讓我們開始吧!

一月(無主題)
一月開始時,Exercism 團隊還在挑選每月主題,所以那個月的語言由我自由選擇。在沒有方向的情況下,我以 Go 揭開這一年的序幕。我在 2022 年年中上過這門語言的速成課,但之後沒怎麼用,完全不覺得自己上手。
Go 是門有趣的語言。它嚴格的編譯器意味著你的程式一定會正確,在編譯器認為你可以繼續之前,你一步也動不了。1 它處理錯誤的方式相當囉嗦,好處是你永遠不會措手不及(代價是要寫 if err != nil { return err } 寫上 好多好多次)。它很擅長把難事變簡單(例如透過 channel 實現平行處理),但也會把一些簡單的事變難(字串操作)。它有健全的標準函式庫,意味著不用第三方模組就能完成大部分任務。我喜歡這個生態系有那麼多部分(格式化、安裝、建置等)都是官方內建,直接整合在 go 指令裡。這門語言有它的批評者,但我認為它在正確性和可維護性這兩個目標上大致是成功的。
我還沒喜歡到會把它當作首選,但對於效能敏感的程式來說,它是個很棒的工具,像是用來顯示 shell 提示字元中的巢狀路徑。
函式二月

二月直接跳進深水區,介紹函式語言,這是常見的命令式程式語言中偏數學的分支。函式語言以其「純」函式(沒有副作用)聞名。我選擇了 Elixir,主要是因為我朋友 Caleb 用它在 Advent of Code 解題,對它讚不絕口。
我相當享受使用 Elixir 的時光。它的靈感來自 Ruby(這說得通,它的創造者 José Valim 是 Rails 的核心貢獻者)。我發現用它來表達函式概念,例如方法鏈,相當簡單。我很喜歡讓這件事變簡單的各種語法糖,例如管線運算子(|>):
foo(bar(baz(new_function(other_function()))))
# becomes
other_function() |> new_function() |> baz() |> bar() |> foo()
那也是我第一次接觸巨集,也就是會寫程式的程式碼。因為 Elixir 程式可以表示成抽象語法樹,而它本身就是合法的 Elixir 程式碼,所以要寫出能產生其他合法程式碼的程式很容易。這是個很酷的概念,而 Elixir 讓它變得簡單。我也很喜歡函式可以根據引數的形狀進行模式比對,因此函式呼叫可以被導向適當的實作:
defmodule TuplePrinter do
def print({a}) do
IO.puts("single")
IO.puts(a)
end
def print({a, b}) do
IO.puts("double")
IO.puts(a)
IO.puts(b)
end
end
TuplePrinter.print({1})
TuplePrinter.print({2, 2})
# single
# 1
# double
# 2
# 2
這似乎是那種不是超棒、就是讓你的程式碼變成一團義大利麵的功能。不管怎樣,這是個很酷的概念!
Elixir 也受益於執行在 Erlang 的 BEAM 虛擬機之上,讓它有個龐大的生態系可以互通。它很擅長並行處理,也是廣受喜愛的 Phoenix 網頁框架的核心。
雖然我沒有立即需要使用 Elixir,但我覺得用它寫程式真的很有趣,未來也絕對願意再回來碰它。而且,用不熟悉的方式(也就是遞迴)來解決熟悉的問題,也是個有趣的挑戰。
機械三月
三月聚焦於「系統」語言,也就是會編譯成機器碼的語言。
在所有選項中,Go 是我唯一感興趣的語言。2 眼尖的讀者會發現,我已經用過一個月的 Go,所以重複它並不會算進我的 12 種。這個嘛,我一月挑它時,他們還沒宣布要做主題,所以我沒意識到自己在把自己逼進死角。
如果我早知道 Bun 要出現,我大概會試 Zig,但不幸的是我(還)沒辦法預知未來。所以,在沒有更吸引人的選擇下,我又多用了一個月的 Go,心裡清楚之後得在這一年的某個時間點補上另一種語言。
分析四月
四月的主題是資料科學常用的語言。我對 Python 太熟了,大學時在統計課用過 R(而且不怎麼喜歡),所以就選 Julia 吧!
我挺享受使用它的時光,但主要是因為它感覺和 Python 太像了。這讓人有點不安,就像美國人待在加拿大一樣。一切感覺都很熟悉,卻又_稍微_不太一樣,讓人難以確切說出是哪裡不同。突然,有人遞給你一枚兩元硬幣(或是一個_真的_非常適合做矩陣運算的函式),你才意識到自己已經不在堪薩斯了。
最讓我印象深刻的是 Julia 的型別系統。它可以(選擇性地)加上標註,像 Python 的型別系統那樣,但會在執行時檢查以確保引數符合宣告的型別。我認為 Python 的系統在整合工具和不擋路之間取得了恰到好處的平衡,但我承認 Julia 對型別錯誤的函式發出的執行時錯誤也很有用。
總的來說 Julia 很酷,但我不預期未來會需要用到它。
思維轉換五月
五月更進一步強調「嘗試新事物」,特別介紹了做法非常與眾不同的語言。我趁這個機會試了始終很受歡迎的 Rust。我得說,我懂為什麼大家這麼瘋它。
雖然惡名昭彰的借用檢查器確實需要時間適應,但我喜歡它讓我更謹慎地思考自己的程式。編譯器確實很嚴格,但它的錯誤訊息遠超乎期待,幫我修正問題。我不會說我第一週特別有效率,但我覺得至少能看見學習曲線的頂端了。
Rust 的套件管理器 cargo 也值得特別一提。雖然我沒有安裝任何第三方套件,但它的建置、測試和格式化功能都很棒。它的 VSCode 擴充功能也一樣,具備我會期待從 Rust 這類靜態型別語言得到的各種貼心功能。良好的開發者體驗真的會帶來天壤之別。
雖然兩者在實作層面非常不同,但就我會拿它們來做的事,也就是讓程式跑得很快而言,Rust 感覺和 Go 很像。我經常使用的語言中,許多工具開始為了它的效能特性轉向 Rust,所以我預期未來會更常見到它(即使我自己不寫 Rust)。
S 表達式之夏(六月)
六月是 S 表達式之月,這是 Lisp 家族中常見的語法形式。我選了 Clojure,一種執行在 JVM 上的函式語言。
很多年前我寫過一點 Clojure。我剛畢業,在第一份工作成了某個關鍵業務每日腳本的唯一維護者。不用說,那是一段棘手的日子。我很好奇,現在的我更年長、更有智慧了,它是不是變得更容易親近一些。
我很開心地報告:是的!二月的函式經驗幫我學會用遞迴思考,而且說真的,它的語法其實沒那麼糟。如果我要在更大的專案裡用它,它與 JVM 的互通性也會很有用。
在有其他選擇的情況下,我看不出自己會拿 Clojure 來做任何事,但這段經歷倒也不是全然不愉快。
支線任務:通用測試執行器!
多年來,我一直用一個小小的 bash 函式來執行目前目錄裡的單元測試。當我用這些新語言工作時,我發現自己為了方便不斷往裡面加東西;記得執行 t 比一次又一次重新學習各語言專屬的測試指令要容易多了。
當所需邏輯超出我對 bash 的掌握程度後,我在六月花了些時間把這個專案獨立出來:通用測試執行器。
我在 Exercism 論壇分享了它,得到不錯的回響。他們非常喜歡,於是我們決定把類似的功能直接做進 Exercism CLI 本身(它是用 Go 寫的,我剛好幸運地才複習過這個主題)。所以在那一年下半,我可以執行 exercism test 來跑當月語言的測試套件(這個指令在通用測試執行器裡原生支援)。
如果你想更了解這個過程,我在它發布時寫了詳細許多的說明。
總之,繼續往下看!
侏羅紀七月
七月展示了古老的語言。就實用性而言,這個月的選擇相當有限。我從歷史悠久的 COBOL 開始,因為我聽說它至今仍支撐著許多關鍵基礎設施。但由於八月有一場婚禮逼近,我沒有餘裕坐下來學一門對我來說如此不同的語言。所以,我改選 Visual Basic,因為它看起來是沒那麼糟的選項。
這裡沒什麼好說的。這門語言似乎有點囉嗦,但用起來還算容易。據我了解,它其實是為了 Windows 上的 UI 開發而設計的,所以做些小練習很難真正了解它的全貌。
應用八月
八月滿是可以用來打造應用程式的語言。不意外地,這個月有很多選擇。我選了 Swift。身為大量使用 Apple 產品的人,他們自家打造的語言對我相當相關。我並非完全沒碰過它,我在 2016 年發布過一款 iOS 應用程式,完全用 Swift 寫成。但之後我就沒再碰過這門語言,而它已經演進了很多,所以我想它應該還是算數。
它用起來有多輕鬆,讓我喜出望外。和這裡許多其他語言相比,Swift 相當新。它於 2014 年首次發布,顯然受益於現代語言設計的經驗教訓。它有官方套件管理器、可選鏈、一級函式,以及合理的字串插值。即使不用 Xcode,讀寫起來也很順手。
話雖如此,Swift 主要還是在 Apple 平台的應用程式情境中有用,而我目前不寫這類東西。雖然它很適合用來做練習,但我不預期短期內會再回來用它。不過我確實很喜歡可以在 iPad 上寫它!
精簡九月
九月探索了非常簡潔或小巧的語言。我選了 jq,一個我用了多年、很喜歡的工具。
不過我一直只把它當成處理 JSON 的工具,而不是通用的程式語言。我驚喜地發現它具備所有常見的配備,也就是函式、變數、迴圈等等,所以我寫得出一些相當複雜的程式:
# input: { "series": "1", "sliceLength": 1 }
. as {series: $series, sliceLength: $sliceLength} |
if
$series == "" then
"series cannot be empty" | halt_error
elif $sliceLength > ($series | length) then
"slice length cannot be greater than series length" | halt_error
elif $sliceLength == 0 then
"slice length cannot be zero" | halt_error
elif $sliceLength < 0 then
"slice length cannot be negative" | halt_error
else
.
end
| [range(0; $series | length)]
| map($series[. : . + $sliceLength])
| map(select(. | length == $sliceLength))
試用我過去簡單的資料轉換從不需要的各種 jq 功能,很有趣。雖然這裡的工具支援有些不足(沒有編輯器整合等等),但更深入理解 jq 功能的廣度仍很有價值。
補充:Mastodon 上的 DJ Adams 讓我注意到 jq-lsp 專案和它對應的 VSCode 外掛。我這次錯過了,但未來會去看看。
物件導向十月
十月深入探討了物件導向語言。我對物件導向設計有份偏愛,因為它很貼近我在腦中想像程式的方式。我選了 Ruby,這聽起來可能是個奇怪的選擇。
我在 Stripe 工作,那裡有全世界最大的 Ruby 程式碼庫。它想必不算「不熟悉」的語言吧?話雖如此,我們的 Ruby 單體應用感覺和「標準」Ruby 相去甚遠:所有東西都用 Sorbet 做型別檢查,有_大量_的程式碼產生,我們還施展了很多魔法來讓一切協同運作並擴展。雖然 Stripe 內外的 Ruby 歸根究柢是同一門語言,但在如此不同的規模下工作會帶來截然不同的體驗;我想知道外面的世界是什麼樣子(也就是距離我大量使用 Ruby 之後的這些年)。
大致上,感覺很好!Ruby 本身很棒,它把「程式設計師的快樂」列為主要目標,這點與我產生共鳴。我喜歡自己常常能猜中從未用過的標準函式庫函式的名字。我喜歡它多容易寫出函式風格的程式碼,以及語法有多順手、多富表達力。
話說回來,我對它的開發者工具比 Python 落後這麼多感到意外。也許我是被寵壞了,但編輯器內的型別提示,以及極快速的 lint 和格式化,對我的重要性比我原先以為的還高。對於一門在巔峰時期如此受歡迎的語言來說,它在這方面感覺落後這麼多,讓我很意外。3 我也一直沒能完全習慣函式呼叫中可省略的括號,這讓把函式當作引數傳遞變得沒那麼直接。
Ruby 仍是門很棒的語言,我也會在工作上繼續用它,但它沒為我做到任何 Python 做不到的事,至少現在是如此。
半位元十一月
十一月是目前最難的一個月:組合語言。雖然現今已不常手寫組合語言,但這是個值得熟悉、既實用又有趣的主題。我選擇 WebAssembly,因為它對現代與未來的網路很重要。雖然它通常被當作編譯目標(而不是手寫的東西),但對於那些狂熱份子,確實存在工具。
我對這個月感到意外地準備充分。語法感覺像 Clojure,語言結構感覺像 Zachtronics 的 TIS-100。說來奇怪,我挺享受每個操作都得從零開始;感覺別有一番古樸風味。如果我真得用這種方式完成任何事,我會很討厭,但在此期間它是個有趣的稀奇玩意。靠著慷慨的註解,我寫出了幾乎還算能讀的東西:
(module
(func (export "eggCount") (param $number i32) (result i32)
(local $res i32) ;; result
(local $remainder i32) ;; loop counter
(loop $loop
;; $res =
(local.set $res
;; $res +
(i32.add
(local.get $res)
;; $number % 2
(i32.rem_u
(local.get $number)
(i32.const 2)
)
)
)
;; $number //= 2
;; (keep on stack)
(local.tee $number
(i32.div_u
(local.get $number)
(i32.const 2)
)
)
;; this will keep looping until remainder is 0
br_if $loop
)
local.get $res
)
)
最大的障礙是缺乏文件和資源。連有哪些全域函式可用都很難得知。但既然我其實不會用到它,一旦上手後也就沒那麼困擾我了。
十二月為這一年收尾,介紹的是不屬於其他分類的語言。因為在三月有重複計算,我這個月需要完成兩種語言。
我從 Wren 開始。它出自 Bob Nystrom 之手,他知名的作品包括Crafting Interpreters。我對它對細節的講究、小巧的佔用空間,以及由上而下的設計感到著迷;一切似乎都設想得很周到。這種程度的用心,在變數作用域和私有性規則的細節中顯而易見。它的編譯器很小且有大量註解,所以如果你對語言的實作有興趣,它是很棒的學習資源。
Wren 有點不夠完善,似乎也大多被放生了,但我覺得對一門玩具語言來說這沒關係。沒有人會抱著能上生產環境的期待來碰它。世界上當然有非生產用語言的容身之處。
還有:Lua
我這個月的第二個選擇是 Lua。和 Wren 相比,它極度實用。它易於嵌入的特性意味著它出現在很多地方,例如 Redis 腳本和 Factorio 模組。它的物件模型需要一點時間適應,但我能看出自己很快就能上手。我很快就喜歡上把 table 當作什麼都能做的結構。工具支援不錯:套件管理器開箱即用,VSCode 擴充功能也順利支援以註解為基礎的型別標註。
雖然我沒有立刻需要 Lua 的地方,但由於它被廣泛使用,它又是個值得放進工具箱的好工具。
總結

我比預期更享受這趟語言之旅。我不只學到一些新的實用技能,視野也感覺徹底開闊了。
至於接下來,我想是更深入學 Rust。它在開發者工具領域的重要性此時已顯而易見,我想確保自己能讀懂並貢獻我所依賴的東西。
我心裡沒有具體的成品目標,但我有一整本 Rust 書要讀、一門我某年報帳的 Rust for JS devs 課程,還有一整個 Exercism track 要完成。我希望至少能貢獻一個開源專案(大概是 Just,我最近很喜歡的程式),但就看這一年會帶我到哪裡了。
在那之前,祝你佳節愉快,2023 年剩下的日子也過得順心!