11 — モジュールとパッケージ
English version: 11_modules_packages.vibe.md
1ファイルが1モジュールです。export と書かない限り他のファイルからは何も
見えず、import と書かない限り自分のファイルには何も入ってきません。
必要なことのほとんどはこの2語です。
2つのファイル
実物です。support/mathx.vibe に export された関数が あります:
export fn triple(x: Int) -> Int {
x * 3
}
この章は相対パスで、欲しい名前を指定して import します:
import ./support/mathx.vibe {
triple
}
fn main with Console {
println("triple(14) = \{triple(14)}")
}
triple(14) = 42
import 行のバリエーション:
import ./lib.vibe { f as renamed }は取り込むときに改名します。
2つのモジュールが良い名前について意見を異にするときのためのものです。
import ./subdir { helper }はディレクトリを import し、その
index.vibe に解決されます。
import @vibe/core { struct MutMap, trait Default }は宣言の種類を
名指しします。値は裸のままです。type / struct / enum / effect / trait
では修飾子は厳密で — Color が enum のときに struct Color を頼むと
拒否されます — 非値 import の裸綴りは互換のためだけに今は受理されて
います。
相対 import はエントリファイル自身のディレクトリより上に出られます —
入れ子のエントリからの import ../../../helper.vibe は解決します。境界に
なるのはエントリのディレクトリではなく、ホストがコンパイラに見せた範囲
(preopen したディレクトリ) です。その外のパスは、../ を何段重ねても
見つかりません。
パッケージをまたぐのはさらに別の境界です。index.vpkg を持つディレクトリ
はその契約を公開するので、export していない名前は黙って import されるので
はなく検査時に拒否されます。
名前で指すパッケージ
@ で始まる import はパスではなくパッケージを指します:
import @vibe/core {
hex_encode, sha1
}
fn main with Console {
println("length(sha1(\"vibe\")) = \{String::length(sha1("vibe"))}")
println("hex_encode(\"hi\") = \{hex_encode("hi")}")
}
length(sha1("vibe")) = 40
hex_encode("hi") = 6869
名前は3箇所を順に探します: プロジェクトの pin 済みストア、ワークスペース
自身の lib/、インストール済みの標準ライブラリ。最初に見つかったものが
勝つので、作業中はローカルのコピーがインストール済みのものを覆い隠します。
契約ファイル
パッケージは、たまたま export されているものを外に出すわけではありません。
公開 API を index.vpkg — 本体のない宣言の契約 — として宣言し、
コンパイラが実装をそれと突き合わせます。食い違えばコンパイルエラーであり、
利用者にとっての驚きにはなりません。
name = @you/counter
version = 1.0.0
description =
#|A tiny counter contract
deps = {}
generated_hash =
type Counter
fn add(x: Int, y: Int) -> Int
定義のない type Counter は、利用者に名前だけを与えて表現は与えない、
という意味です。後から変えられます。宣言の上のヘッダは vibe の構文では
なくパッケージのメタデータで、
docs/adding-modules.md が参照先です。
契約は利用者に向けた境界です。他のパッケージから何に手が届くかを
決めるものであって、パッケージの内側では邪魔をしません。最も近い
index.vpkg が同じ2ファイルは、export して直接 import すれば
ヘルパーを共有できます。契約に書く必要はありません:
pkg/index.vpkg fn package_value() -> Int <- 公開するのはこれだけ
pkg/impl.vibe import ./_helper.vibe { private_value }
pkg/_helper.vibe export fn private_value() -> Int { 41 }
これはコンパイルが通ります。内部ヘルパーは内部のままでいられます — 契約に書くことは、兄弟ファイルから使えるようにする操作ではなく、 公開 API にする操作です。
再現可能なビルド
他人のパッケージに依存するとき、検証されるのはバージョン番号ではなく
内容ハッシュです。pin は自分のパッケージの index.vpkg の先頭、
宣言部より上のメタデータヘッダに書く require 行です:
require @vibe/core 0.2.0 = #pkg:sha1:<40hex>
ビルドのたびにそのハッシュをオフラインで再検査するので、ビルドとビルドの
間、レジストリもネットワークも信頼する必要がありません。値は vibe hash
が計算します。VIBE_REQUIRE_PINS=1 を設定すると pin の無い依存はエラーに
なります。リリースビルドはそうあるべきです。
(第1章の vibe.deps は別の、より粗い仕組みです: vibe add / vibe fetch
がリポジトリごと deps/ に vendor するための <name> <url> 行を並べます。
require pin はレジストリのレーンで、パッケージ単位・内容アドレスで、
コンパイラ自身が検査します。)
公開する
誰かに渡す準備ができたら:
vibe pkg publish lib/@you/pkg # バージョン検査の後、ログに追記
vibe pkg install @you/pkg@1.0.0 # 取得してログと照合
vibe pkg add github:owner/repo/dir@ref
vibe pkg yank @you/pkg@1.0.0 # バージョンを取り下げる
vibe pkg update @you/pkg # 最新へ移動し、契約の差分を表示
publish と yank は透明性ログに追記され、install はその証明を検証します — なので、後からバージョンを差し替えられることはありません。設計は docs/registry-design.md にあります。
次: テストを書く。