【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】
使用 Dart 進行 hack
Python 的互動模式很棒。Dart 目前沒有互動模式,但它非常適合快速製作原型,所以讓我們看看是否可以將一些東西拼湊在一起!
聲明:我的確在 Google 工作,但這篇文章是關於一個個人專案。我不在 Dart 團隊或相關團隊。這只是我的淺見和我的故事。一起來吧。
像 Python 或 Ruby 等語言的互動模式有助於讓初學者更容易上手。但這些並不是唯一使用 REPL 概念的語言。REPL 是一個讀取-評估-列印循環。當您在終端機中與它們互動時,像 BASH 或 zsh 等 shell 也會使用 REPL。這也是您在 Jupyter (IPython Notebooks)、Matlab、Mathematica 或 Maple 中使用筆記本時所獲得的。這種互動式計算方式在研究人員中非常流行。
這就是 Python 互動模式的樣子:
Dart 不支援像 Python 那樣在全域範圍內評估語句,因此沒有明顯的方法可以正確地執行此操作。在 Dart 中,語句必須在函式內,而 main 函式是程式的進入點。就像在 C++ 或 C# 中一樣。就個人而言,我更喜歡這種方式,因為它更容易理解程式運行時發生的事情。但是… 我仍然非常想要一個互動模式。它可以讓我玩轉想法,更快地嘗試事物。那麼讓我們看看是否可以建立一個 REPL 作為概念驗證!
如何在 Dart 中建立 REPL?
Dart 非常適合製作原型。所以讓我們這樣做,不要被語言設計問題所困擾 :)
現在沒有像 JavaScript 或 Python 中那樣的 eval 函式,而且我也不想自己寫一個完整的直譯器來實現它。它不會很快,而且我也沒有很多時間。但是,當您在 Intellij 中除錯 Dart 程式碼 時,您可以在逐步執行程式碼時評估表達式。評估表達式正是我們想要做的,不是嗎?我們可以使用這個功能嗎?
Dart 的除錯功能透過其 VM 服務 公開。它是 Dart VM 提供的 JSON-RPC 服務,您可以連接到它來除錯您的應用程式。Natalie Weizenbaum 發表了一篇關於 vm_service_client
套件的文章,該套件提供了一個非常好的 API 來與 VM 服務互動:http://news.dartlang.org/2016/05/unboxing-packages-vmserviceclient.html
現在我們可以做的是:我們的 REPL 可以連接到它自己的 VM 服務來評估它從終端機讀取的表達式!這聽起來很瘋狂…
… 但它有效!我寫了一個 快速 spike,它確實有效。Dart 支援 非同步程式設計,這非常方便,因為它可以防止程式在與自己的 VM 服務通訊時阻塞自己。
Spike 是測試驅動開發中的一個概念:它們是用於找出一些技術問題的快速且粗糙的實驗。
解決了可行性問題後,很容易編寫一個合適的概念驗證。為了評估超出 1 + 1 的表達式,我們需要支援變數。現在無法輕鬆建立變數,因為變數宣告在 Dart 中不是表達式。在 Python 中,您可以透過賦值來即時宣告變數。在 Dart 中,我們可以透過重載 noSuchMethod 來即時在字典中建立元素,從而模擬動態欄位。我稱這個類別為 Scope,當我們在它的實例中 評估表達式 時,可以像全域變數一樣存取欄位。
程式碼實際上更直接:
有了這個,我們已經可以做一些事情,例如 a = 3
和 b = a * 3
:
我們很快就會遇到一個限制,那就是我們只能存取在宣告 Scope 類別的檔案中匯入的符號。import '...';
無法使用 VM 服務進行評估。因此,如果我們沒有明確匯入 dart:io
,就沒有 dart:io
,也沒有自訂函式庫 :(
哦,等等!Dart 可以使用帶有 Isolate.spawnUri 的 URI 產生新的 isolates(獨立的工作線程)。使用者可以在命令列上指定額外的匯入,REPL 可以產生程式碼以包含這些匯入,然後使用產生的程式碼產生新的 isolate,這些程式碼具有使用者可用的匯入。
它有效 \o/
支援更多 Dart
現在的另一個問題是我們只能評估表達式。像 if/else
塊或 while
迴圈這樣的控制語句不是表達式。對於語句,我們可以將它們包裝在一個閉包中並執行該閉包,這是一個函式調用表達式。因此,if (a == 1) print('a is 1!!');
將變成
1 | () { if (a == 1) print('a is 1!!'); }(); |
我們只需要弄清楚輸入是表達式還是語句。這很困難,因為我們必須為此編寫一個 Dart 解析器。但是 Dart 是用 Dart 編寫的,而且 analyzer 套件提供了一個可以免費解析任何 Dart 程式碼的解析器!
因此,這也解決了。
更多匯入
最後一點阻止我們匯入任何函式庫的是,預設情況下,新的 Isolate 只能看到在其 pubspec.yaml
中提到的套件。我們希望支援匯入任何函式庫。Isolate.spawnUri
有一個 packageConfig
參數,允許我們指定從套件名稱到套件路徑的映射。我們可以使用另一個命令列參數來定位另一個套件,並在我們的 Isolate 中使用其套件設定。喔!
我們很快就會遇到問題,我們的 Isolate 需要存取 analyzer 套件(和其他套件),而這些套件可能不會被您想要在 REPL 會話中使用的任何套件載入。套件 package_resolver 來救援!有了它,我們可以輕鬆地操作套件設定。
有了這些,我們就實作了一個完整的工作流程:
接下來是什麼?
這是一篇很長的文章… 整個概念驗證的程式碼量為 451 行,幾乎和這篇文章一樣長。
程式碼可以在 https://github.com/BlackHC/dart_repl/tree/master/lib 上找到。如果您安裝了 Dart,您可以輕鬆地試用它:
1 | pub global activate dart_repl |
我真的很喜歡在我的閒暇時間建立這個概念驗證。所有部分都在幾個小時內就位。Intellij 中對 Dart 的 IDE 支援非常出色,而且現在有很多文件和文章。例如,請查看 Natalie Weizenbaum 的 Unboxing Packages 系列:http://news.dartlang.org/2016/04/unboxing-packages-async-part-3.html 等。Dart 中的低階 hack 很有趣,而且有很多很棒的函式庫可以發揮創意。code_builder 看起來非常有前途,built_collection 提供了不可變的集合。David Morgan 也一直在發表關於 Dart 中 不可變集合 的文章。
對於 dart_repl
,如果可以在運行時匯入額外的函式庫而無需重新啟動,那就太好了。Dart 團隊最近在 VM 中加入了對熱重載的支援。這主要為 Flutter 中的行動應用程式開發人員提供了更好的體驗。也許,這也可以用於臨時匯入以及在 REPL 中定義函式和類別。
總體而言,Dart 對於研究和研究人員來說可能非常有用,我絕對希望看到 Jupyter 支援 Dart。只需要實作其核心介面,就可以使這樣的 Dart REPL 與之相容… :) 那應該很容易,對吧?
Dart REPL 概念驗證 最初發佈在 Medium 的 dartlang 上,人們在那裡透過醒目顯示和回應這個故事來繼續討論。