Toolbox languages

Hillel Wayne

工具箱語言

原文由 Hillel Wayne 發布,訂閱此部落格

所謂的工具箱語言,就是那種不需要依賴第三方套件就能很好解決問題的程式語言。我平常用的工具箱語言是 Python 和 shell 指令稿,這些你大概都已經知道了。接下來要介紹的是一些比較冷門的選擇。

AutoHotKey

這個一定要提!AutoHotKey 基本上就是「給 GUI 用的 shell 指令稿」。它是個超棒的工具,能讓那些本身不支援程式化的應用程式變得好用許多。它只能在 Windows 上跑,不過 Mac 和 Linux 上也有類似的工具。

實用功能:

  • 你可以設定只在特定程式中生效的快捷鍵,或是只有在某個全域旗標被啟用、螢幕上出現特定文字時才會生效的快捷鍵等等。
  • 可以很輕鬆地取用大量基礎的 win32 功能。比方說要打開檔案選擇對話框,只要寫 f := FileSelect() 就好。
  • 它的 GUI 框架真的、真的非常好用。老實說,就我用過的語言來說,至少在做小東西時,它是最好的。

範例問題:

Audacity 不讓你設定滑鼠快捷鍵,所以我用 AutoHotKey 把滑鼠中鍵對應到鍵盤快捷鍵。

#HotIf WinActive("ahk_exe audacity.exe")
  MButton::Send "^l" ; silence selection
#HotIf

我讓輸入 ;iso 就會自動填上當天的日期。

:R:;iso:: 
{
    Send(FormatTime(,"yyyy-MM-dd"))
}

這是我用來做附帶時間戳記筆記的小工具。

; right-ctrl + d
>^d::
{
  TimeString := FormatTime(,"MM/dd hh:mm tt")
  t_msg := InputBox(,TimeString,"w200 h100")
  if t_msg.Result = "OK" {
    timestampfile := A_WorkingDir . "\Config\timestamps.txt"
    FileAppend(TimeString . "`t" . t_msg.Value . "`r`n", timestampfile)
  }
}

其他用途:替工具箱語言啟動 REPL。輸入通關某款遊戲所需的 100 次按鍵序列(懂的人就懂)。

延伸閱讀:

J

這是一個陣列語言,就像 APL 一樣。處理陣列運算時非常強大,但只要碰到字串或結構化資料,就會讓人抓狂。我以前很常用它,但現在大多改用其他工具了,像是 Excel 和 Raku。不過在它擅長的領域,它依然非常厲害。

實用功能:

  • 極度精簡。在多數語言裡要寫好幾行的東西,在 J 裡只要幾個字元就能搞定,所以我喜歡用它來快速做一堆數學運算。
  • 一級的多維陣列。+ 可以把兩個數字相加、把兩個陣列逐元素相加、把一個數字加到陣列的每個元素上,或是把一個陣列加到更高維度陣列的每一列(或每一欄)。
  • 有很多頂層的基本運算(primitive),專門處理特殊的數學操作,像是把一個數字分解成質因數。

範例問題:

取得一個數字的所有質因數:

    q: 2520
2 2 2 3 3 5 7

假設有兩個行程,各自執行一個四步驟的演算法,會有多少種可能的交錯執行方式

    ni =: !@:(+/) % */@:!
    ni 4 4
70

如果我想列出 1 到 3 個處理器、1 到 3 個步驟各自對應的交錯數量表格呢?

   (ni@:$)"0/~ >: i. 3
1  1    1
2  6   20
6 90 1680

延伸閱讀:

Frink

這可能是這份清單上最冷門的語言。Frink 是為了因次分析(也就是帶單位的數學)而設計的,但它也塞進了一堆開發者覺得有趣的功能——而且還真不少!它大概是我見過的程式語言裡,最接近「更好的計算機」的一個:容易上手、功能強大,又沒有 J 或 Raku 那種陌生的語法。

實用功能:

  • 內建大量的單位和單位修飾詞。calendaryear 正好是 365 天,tropicalyear 是 365.24 天,而 half nanocentury 大約是 1.6 秒。
  • 日期字面量語法:# 2000-01-01 # - # 200 BC # 會得到 2199.01 年。
  • 內建用來處理不確定性的區間型別。有點笨拙,但堪用。

範例問題:

如果有人在 1970 年 1 月 1 日午夜出生,那他什麼時候會滿十億秒大?

   # 1970 # + 1 billion seconds
AD 2001-09-09 AM 02:46:40.000 (Sun) Central Daylight Time

如果我用某個時間跑完某段距離,我的平均配速是多少?

   // In miles per hour
   2.5 miles / (27 minutes + 16 seconds) -> mph
5.5012224938875305623

   // In meters per hour
   2.5 miles / (27 minutes + 16 seconds) -> meters / hour
8853.3594132029339854

   // In (minutes, seconds) per mile
   1 / (4.5 miles / hour) -> [minutes/mile, seconds/mile, 0]
13, 19

(6 ± 2)*(8 ± 1)是多少?

   x = new interval [2, -2]
   (6 + x) * (8 + x/2)
[28, 72] // range is between 28 and 72

延伸閱讀:

Raku

Raku(原名 Perl 6)是一個非常奇怪的語言,充滿了各種黑魔法。它非常強大,但也非常容易搞砸。我還不太敢拿它來跑正式環境的程式。但拿來寫個人腳本和工具箱用途,它就非常驚人。

實用功能

  • 你可以自己定義中綴運算子!還有後綴運算子。以及環綴運算子
  • 多到嚇人的語法糖,多到讓我有點擔心的程度。比方說,你不用寫 [1, 2],可以直接寫 <1 2>。也不用寫 ["a", "bc"],直接寫 <a bc> 就好。Raku 就是 Just Knows™ 該怎麼做。
  • 如果你定義了一個 MAIN 函式,它的參數就會自動變成 CLI 參數。
  • 基於執行期數值的多分派多重方法。把它跟 MAIN 結合起來,要做小型的 CLI 工具就變得非常容易。
  • 許多數學運算子都有對應的 Unicode 符號(例如用 ∈ 來表示 (elem)),這跟我的 AutoHotKey 熱字串搭配起來效果絕佳。

範例問題

產生三個隨機的 10 個字元小寫字串。

> for ^3 {say ('a'..'z').roll(10).join}
fflqymxapa
znyxehaqvo
qwqxusudqw

文法(grammars)來解析不常見的結構化資料格式(見連結)。

把一堆 SVG 的 id 複製到 inkscape 標籤裡。

use XML;

my $xml = from-xml-file("file.svg");

for $xml.elements(:NEST, :RECURSE<99>) -> $e {
  with $e<id> ~~ /k\w/ {
    say $_.target;
    $e.set("inkscape:label", $_.target);
  }
}
$xml.save()

寫一個帶有幾個繁瑣選項組合的 CLI(範例)。

延伸閱讀:

Picat

這是我最新的工具箱語言,也是讓我開始思考工具箱這個概念的語言。它是邏輯程式設計、約束求解和指令式逃生艙口的強烈混合體。我一開始是把它當成「更好的約束求解器」來學的,後來卻不斷發現它的新用途。

實用功能:

  • 可以對變數賦值。在邏輯語言裡這意外地非常有用。很多問題感覺幾乎很適合用邏輯程式設計來解,但總會有一小部分演算法讓我想破頭也想不出要怎麼 100% 用邏輯來表達。指令式就提供了我需要的逃生艙口。
  • planner 模組。我超愛 planner 模組。它是我的好麻吉。給它一個目標和一串可能的動作,Picat 就會找到一連串能達成目標的動作。超級酷。

範例問題:

如果我以 4.5 英里/小時跑了 X 分鐘,再以 5.1 英里/小時跑了 Y 分鐘,要怎麼選 X 和 Y 才能在 38 分鐘內跑完 3.1 英里?

import cp.
[X, Y] :: 1..60, 45*X + 51*Y #= 31*60,  X+Y #= 38, solve([X,Y]).
X = 13
Y = 25
yes

給定一堆活動、時間限制和互斥條件,規劃出一個假期行程

檢查一個邏輯謎題是否有多組解。檢查邏輯謎題的線索是否有多餘,或是否可以移除其中一條線索仍維持唯一解。

快速做出一個 Petri net 的可達性求解器

延伸閱讀:

什麼造就了好的工具箱語言?

我見過的大多數好的工具箱語言都是用來做計算的。我認為用來處理效應(effects)和自動化的工具箱語言也是可行的(像 AutoHotKey 這樣),只是這個領域比較少人探索。

一個工具箱語言應該要寫起來非常、非常快。至少要比 Python 快。比較一下「十對隨機數字」怎麼寫:

# python

from random import randint
[(randint(10), randint(10)) for _ in range(10)]

# Raku
^10 .roll(2) xx 10

# J
10 2 ?@$ 10

有幾個因素會促成這點:精簡的語法代表要打的字更少。大量內建功能代表不用自己寫一堆基礎的東西。從標準函式庫匯入雖然不是最理想,但還可以接受。需要另外安裝第三方套件就讓我很困擾了。Raku 在這方面做得很酷;Rakudo Star Bundle 就預先打包了一堆實用的社群套件。

如果你能用一行就搞定某件事,你就可以直接丟進 REPL 裡跑。所以你會想要一個好的 REPL。我用的大多數語言都有不錯的 REPL,不過我想我的 Lisp 和 Smalltalk 讀者們對於什麼叫「好的 REPL」肯定會有不同的看法。

理想上,語言應該要有平緩的學習曲線。Raku 雖然很複雜,但你只要學一點點就能派上用場,而 J 的學習曲線就太陡了,不太適合推薦給大多數人。不過,這點往往會跟「寫起來快」有所衝突。

我想放進工具箱的其他工具

  • jq 用來處理 JSON
  • JavaScript,這樣我就能透過開發者控制台去修改別人的網站
  • 某種 APL,能提供 J 的好處,但又不會有我一直遇到的那些挫折
  • 一個串接式程式語言(concatenative PL),如果我哪天搞清楚 CPL 到底適合解決什麼小問題的話
  • 某種能讓網頁爬取和解析變得跟計算一樣簡單的東西。Requests 跟 bs4 才不是。

感謝 Saul Pwanson 提供的回饋。如果你喜歡這篇文章,歡迎訂閱我的電子報!我每週都會在那裡發表新的文章。

我為企業提供形式化方法的培訓,讓軟體開發更快、更便宜、更安全。歡迎點此了解更多。

我的新書 Logic for Programmers 現已開放搶先體驗!請點此查看。

本文章由 muse-spark-1.2-contributor 進行翻譯

留言