使用 Dune 的函式庫

先決條件

簡介

Dune 提供了幾種將模組安排到函式庫中的方法。我們將研究 Dune 用於建構包含模組的函式庫專案的機制。

本教學課程使用 Dune 建置工具。請確保您已安裝 3.7 或更新版本。

最小專案設定

本節詳細說明了幾乎最小的 Dune 專案設定結構。請查看您的第一個 OCaml 程式以使用 dune init proj 命令自動設定。

$ mkdir mixtli; cd mixtli

在此目錄中,建立四個檔案:dune-projectdunecloud.mlwmo.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 (Ns)
Cumulonimbus (Cb)

這是目錄內容

$ 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 模組,其中包含兩個子模組:StratusCumulus

以下是不同的名稱

  • 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.mlilib/status.mli。稍後,在「包含子目錄」部分中,檔案 lib/cumulus/m.mlilib/status/m.mli 也相同。

以下是使用命名模組類型(也稱為簽章)來修正此問題的一種可能方式。首先,刪除檔案 lib/cumulus/m.mlilib/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.MStratus.M 會明確地繫結到模組 Wmo 中定義的相同介面。

停用程式庫包裝

本節詳細說明 Dune 如何將程式庫的內容包裝到專用模組中。它也說明了如何停用此機制。

lib 資料夾的內容被修剪回接近程式庫部分中的狀態。刪除檔案 lib/cumulus/m.mllib/stratus/m.mllib/wmo.mlilib/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 是包裝模組,具有 CumulusStratus 作為子模組。檔案 lib/cumulus.mllib/stratus.ml 的編譯分別產生模組 Wmo__CumulusWmo__StratusWmo 的前者子模組是後者的別名。

包裝 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))

在這種情況下,「程式庫」僅包含 CumulusStratus 模組,它們並排綑綁在一起。在 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 提供了幾種將模組安排到程式庫中的方法。

仍然需要協助嗎?

協助改進我們的文件

所有 OCaml 文件都是開放原始碼。發現有錯誤或不清楚的地方嗎?提交提取請求。

OCaml

創新。社群。安全。