使用 Dune 的函式庫
簡介
Dune 提供了幾種將模組安排到函式庫中的方法。我們將研究 Dune 用於建構包含模組的函式庫專案的機制。
本教學課程使用 Dune 建置工具。請確保您已安裝 3.7 或更新版本。
最小專案設定
本節詳細說明了幾乎最小的 Dune 專案設定結構。請查看您的第一個 OCaml 程式以使用 dune init proj
命令自動設定。
$ mkdir mixtli; cd mixtli
在此目錄中,建立四個檔案:dune-project
、dune
、cloud.ml
和 wmo.ml
dune-project
(lang dune 3.7)
(package (name wmo-clouds))
此檔案包含全域專案組態。它幾乎保持在最小值,包括指定所需 Dune 版本的 lang dune
段落,以及簡化本教學課程的 package
段落。
dune
(executable
(name cloud)
(public_name nube))
每個需要某種建置的目錄都必須包含 dune
檔案。executable
段落表示會建置一個可執行程式。
name cloud
段落表示檔案cloud.ml
包含可執行檔。public_name nube
段落表示可使用名稱nube
來使用可執行檔。
wmo.ml
module Stratus = struct
let nimbus = "Nimbostratus (Ns)"
end
module Cumulus = struct
let nimbus = "Cumulonimbus (Cb)"
end
cloud.ml
let () =
Wmo.Stratus.nimbus |> print_endline;
Wmo.Cumulus.nimbus |> print_endline
這是產生的輸出
$ opam exec -- dune exec nube
Nimbostratus ()
Cumulonimbus ()
這是目錄內容
$ tree
.
├── dune
├── dune-project
├── cloud.ml
└── wmo.ml
Dune 將其建立的檔案和來源副本儲存在名為 _build
的目錄中。您無需在那裡編輯任何內容。在使用 Git 管理的專案中,應該忽略 _build
目錄
$ echo _build >> .gitignore
您也可以設定您的編輯器或 IDE 來忽略它。
在 OCaml 中,每個 .ml
檔案都會定義一個模組。在 mixtli
專案中,檔案 cloud.ml
定義了 Cloud
模組,檔案 wmo.ml
定義了 Wmo
模組,其中包含兩個子模組:Stratus
和 Cumulus
。
以下是不同的名稱
mixtli
是專案的名稱(在納瓦特爾語中表示雲)。cloud.ml
是 OCaml 原始程式碼檔案的名稱,在dune
檔案中稱為cloud
。nube
是可執行命令的名稱(在西班牙語中表示雲)。Cloud
是與檔案cloud.ml
相關聯的模組名稱。Wmo
是與檔案wmo.ml
相關聯的模組名稱。wmo-clouds
是此專案建置的套件名稱。
dune describe
命令允許查看專案的模組結構。以下是其輸出
((root /home/cuihtlauac/caml/mixtli-dune)
(build_context _build/default)
(executables
((names (cloud))
(requires ())
(modules
(((name Wmo)
(impl (_build/default/wmo.ml))
(intf ())
(cmt (_build/default/.cloud.eobjs/byte/wmo.cmt))
(cmti ()))
((name Cloud)
(impl (_build/default/cloud.ml))
(intf ())
(cmt (_build/default/.cloud.eobjs/byte/cloud.cmt))
(cmti ()))))
(include_dirs (_build/default/.cloud.eobjs/byte)))))
函式庫
在 OCaml 中,函式庫是模組的集合。預設情況下,當 Dune 建置函式庫時,它會將綑綁的模組包裝到一個模組中。這允許在同一個專案中,不同的函式庫內有多個名稱相同的模組。此功能稱為模組名稱的命名空間。這與模組對定義所做的事情類似;它們避免了名稱衝突。
Dune 從目錄建立函式庫。讓我們看一個例子。這裡的 lib
目錄包含其來源。這與 Unix 標準不同,在 Unix 標準中,lib
儲存已編譯的函式庫二進位檔。
$ mkdir lib
lib
目錄會填入以下原始程式碼檔案
lib/dune
(library (name wmo))
lib/cumulus.mli
val nimbus : string
lib/cumulus.ml
let nimbus = "Cumulonimbus (Cb)"
lib/stratus.mli
val nimbus : string
lib/stratus.ml
let nimbus = "Nimbostratus (Ns)"
lib
目錄中找到的所有模組都會綑綁到 Wmo
模組中。此模組與我們在 wmo.ml
檔案中的模組相同。為了避免冗餘,我們將其刪除
$ rm wmo.ml
我們更新 dune
檔案以建置可執行檔,以將函式庫用作相依性。
dune
(executable
(name cloud)
(public_name nube)
(libraries wmo))
注意事項:
- Dune 從目錄
lib
的內容建立模組Wmo
。 - 目錄的名稱(此處為
lib
)無關緊要。 - 函式庫名稱在
dune
檔案中以小寫形式顯示 (wmo
)- 在其定義中,在
lib/dune
中 - 當在
dune
中用作相依性時
- 在其定義中,在
函式庫包裝模組
預設情況下,當 Dune 將模組綑綁成程式庫時,它們會自動被包裝成一個模組。可以手動編寫包裝檔案。包裝檔案的名稱必須與程式庫相同。
在這裡,我們正在為前一節的 wmo
程式庫建立一個包裝檔案。
lib/wmo.ml
module Cumulus = Cumulus
module Stratus = Stratus
以下是如何理解這些模組定義
- 在左側,
module Cumulus
表示模組Wmo
包含一個名為Cumulus
的子模組。 - 在右側,
Cumulus
指的是在檔案lib/cumulus.ml
中定義的模組。
執行 dune exec nube
,你會發現程式的行為與前一節相同。
當一個程式庫目錄包含一個包裝模組(這裡的 wmo.ml
)時,只有它會被公開。該目錄中所有其他未出現在包裝模組中的基於檔案的模組都是私有的。
使用包裝檔案可以實現幾件事
- 擁有不同的公開和內部名稱,
module CumulusCloud = Cumulus
- 在包裝模組中定義值,
let ... =
- 公開函子應用產生的模組,
module StringSet = Set.Make(String)
- 將相同的介面類型應用於多個模組,而無需重複檔案
- 透過不列出模組來隱藏它們
包含子目錄
預設情況下,Dune 會從與 dune
檔案位於同一目錄的模組建構程式庫,但它不會查看子目錄。可以變更此行為。
在此範例中,我們建立子目錄並將檔案移動到那裡。
$ mkdir lib/cumulus lib/stratus
$ mv lib/cumulus.ml lib/cumulus/m.ml
$ mv lib/cumulus.mli lib/cumulus/m.mli
$ mv lib/stratus.ml lib/stratus/m.ml
$ mv lib/stratus.mli lib/stratus/m.mli
使用 include_subdirs
節來變更預設行為。
lib/dune
(include_subdirs qualified)
(library (name wmo))
更新程式庫包裝,以公開從子目錄建立的模組。
wmo.ml
module Cumulus = Cumulus.M
module Stratus = Stratus.M
執行 dune exec nube
,你會發現程式的行為與前兩節相同。
include_subdirs qualified
節會以遞迴方式運作,除非在包含 dune
檔案的子目錄中。請參閱 Dune 文件,以了解更多關於此主題的資訊。
移除重複的介面
在之前的階段中,介面被重複。在本教學的「程式庫」部分中,有兩個檔案相同:lib/cumulus.mli
和 lib/status.mli
。稍後,在「包含子目錄」部分中,檔案 lib/cumulus/m.mli
和 lib/status/m.mli
也相同。
以下是使用命名模組類型(也稱為簽章)來修正此問題的一種可能方式。首先,刪除檔案 lib/cumulus/m.mli
和 lib/status/m.mli
。然後修改模組 Wmo
的介面和實作。
wmo.mli
module type Nimbus = sig
val nimbus : string
end
module Cumulus : Nimbus
module Stratus : Nimbus
wmo.ml
module type Nimbus = sig
val nimbus : string
end
module Cumulus = Cumulus.M
module Stratus = Stratus.M
結果相同,只是實作 Cumulus.M
和 Stratus.M
會明確地繫結到模組 Wmo
中定義的相同介面。
停用程式庫包裝
本節詳細說明 Dune 如何將程式庫的內容包裝到專用模組中。它也說明了如何停用此機制。
lib
資料夾的內容被修剪回接近程式庫部分中的狀態。刪除檔案 lib/cumulus/m.ml
、lib/stratus/m.ml
、lib/wmo.mli
和 lib/wmo.ml
。以下是我們需要的唯一檔案的內容
lib/dune
(library (name wmo))
lib/cumulus.ml
let nimbus = "Cumulonimbus (Cb)"
let altus = "Altocumulus (Ac)"
lib/stratus.ml
let nimbus = "Nimbostratus (Ns)"
在此設定中,執行 dune utop
可讓您探索可用的內容。
# #show Wmo;;
module Wmo : sig module Cumulus = Wmo.Cumulus module Stratus = Wmo.Stratus end
# #show Wmo.Cumulus;;
module Cumulus : sig val nimbus : string val altus : string end
# #show Wmo.Stratus;;
# module Stratus : sig val nimbus : string end
# #show Wmo__Cumulus;;
module Wmo__Cumulus : sig val nimbus : string val altus : string end
# #show Wmo__Stratus;;
# module Stratus : sig val nimbus : string end
定義了五個模組。Wmo
是包裝模組,具有 Cumulus
和 Stratus
作為子模組。檔案 lib/cumulus.ml
和 lib/stratus.ml
的編譯分別產生模組 Wmo__Cumulus
和 Wmo__Stratus
。Wmo
的前者子模組是後者的別名。
包裝 Wmo
可以手動編寫。這個範例說明了如何限制包裝子模組的介面。
lib/wmo.ml
module Cumulus : sig val nimbus : string end = Cumulus
module Stratus = Stratus
以下是在 dune utop
中的樣子
# #show Wmo.Cumulus;;
module Cumulus : sig val nimbus : string end
# #show Wmo__Cumulus;;
module Wmo__Cumulus : sig val nimbus : string val altus : string end
可以在 Dune 的組態中停用包裝。
lib/dune
(library (name wmo) (wrapped false) (modules cumulus stratus))
在這種情況下,「程式庫」僅包含 Cumulus
和 Stratus
模組,它們並排綑綁在一起。在 dune utop
中檢查以下內容兩次。一次是在檔案 lib/wmo.ml
不變的情況下,第二次是在刪除該檔案之後。
# #show Cumulus;;
module Cumulus : sig val nimbus : string val altus : string end
# #show Stratus;;
module Stratus : sig val nimbus : string end
注意事項:
- 當檔案
lib/wmo.ml
存在時,未列出該檔案的modules
節會阻止它被綑綁在程式庫中 - 當檔案
lib/wmo.ml
不存在時,wrapped false
節會阻止建立Wmo
包裝
結論
OCaml 模組系統允許以多種方式組織專案。Dune 提供了幾種將模組安排到程式庫中的方法。