-
Notifications
You must be signed in to change notification settings - Fork 84
v0.1.z でのパッケージシステムの設計案
従来のv0.0.zのSATySFiはパッケージと呼ばれる概念がありつつもその立ち位置が曖昧で,ほぼモジュールと1対1対応する形で使用していました.SATySFi v0.1.0では,パッケージという概念をリリースの単位をなすものとしてはっきりとモジュールとは区別して扱うことにし,本稿に示したようなパッケージシステムを採用したいと考えています.これは以下のような理由によります:
- 複数の “パッケージ” が同一のモジュール名を使っているような状況でも,名前が衝突したりせず整然と扱えるようにしたいため.
- ソースコードに限らず,フォントをはじめとする種々のリソースもパッケージという単位で扱えた方がやはり名前の衝突防止などに関して自然であるため.
- 外部ツールとSATySFiに分かれている場合に比べ,「文書ファイル内に依存パッケージの制約を記述する」といった機能が自然に実現できるなど,ユーザにとっての使用感上恩恵があるため.
従来はこのリリースの単位としてのパッケージをSatyrographosという外部ツールが管理しており,SATySFi本体は関知していませんでしたが,SATySFi v0.1.0ではSATySFi自身がこの意味のパッケージという単位を認識するようにしよう,という試みです.処理系本体の機能が肥大化してややモノリシック気味になってしまうのが弱点ですが,ユーザにとっての弱点はなく,利便性の向上が期待できるのではないかと思います.2022年12月1日現在,すでに dev-0-1-0-package-system
branchにて大枠の実装を終えており,動かすことができます.
既存の別言語としてはElmなども処理系本体がパッケージを扱えるような形態をとっていますが,筆者個人の感想としては使用感が良く,参考にしてはどうかとも思っています.
なお,有志によって開発して頂いているパッケージマネージャSatyrographosとの役割分担などについては,今後開発者やユーザの皆さんから意見を頂いたり相談等させて頂けるとありがたいです.
std-ja-book
クラスおよび tabular
パッケージを利用して sample.saty
という文書を作成したいとします.このとき,まず以下のような sample.saty
の雛形を作成します:
foo/
└── sample.saty
#[dependencies [
(`std-ja-book`, `0.0.1`),
(`tabular`, `0.0.1`),
]]
use open package StdJaBook
use package Tabular
document (|
title = {サンプル文書},
author = {田中 太郎},
|) '<
+p{
こんにちは.
}
>
先頭の #[dependencies …]
が依存するパッケージとそのバージョン制約の指定です.ここでは例えば (`std-ja-book`, `0.0.1`)
によって std-ja-book
パッケージの 0.0.1
またはその後方互換性を壊さない後継バージョンのいずれか(つまり 0.0.1
0.1.0
なるバージョン 0
の場合でもminor versionが変わらない限り後方互換とみなすような少し厳しいsemverを使うことにします)を使いたいということを指定しています.
use open package StdJaBook
は std-ja-book
パッケージが提供するメインモジュール(後述)である StdJaBook
を文書中で使うことを表しています.これを書くことで document
函数が使えるようになります.open
なしで単に use package StdJaBook
とすると,メンバーはスコープに読み込まれず,StdJaBook.document
の形でのみ使えるようになります.
この文書を以下のように solve
コマンドで処理し,パッケージ間の依存関係をもとに必要な各パッケージのバージョンを決定します:
$ satysfi solve sample.saty
すると,(依存関係の制約解消に成功した場合は)各パッケージのバージョンが固定されてソースコードが取得・配置され,それらの情報を記録したロックファイル sample.satysfi-lock
が生成されます:
foo/
├── sample.saty
└── sample.satysfi-lock
続いて build
コマンドにより文書を組みます(ここでロックファイルの内容を参照し,文書ファイルを処理する前に依存パッケージの実装を読みにいきます):
$ satysfi build sample.saty
これがSATySFiの処理の中核で,最終的にPDFを出力します:
foo/
├── sample.saty
├── sample.satysfi-aux
├── sample.satysfi-lock
└── sample.pdf
.satysfi-aux
は従来と同じく相互参照のためのダンプファイルであり,Git管理下に置く必要はありません.
ロックファイルは使いたいパッケージが変わらない限り solve
コマンドで更新する必要はなく,またビルドの再現性のためにGitなどによるバージョン管理の下に加えるのが望ましいものです.新たに依存パッケージを追加したい場合は #[dependencies …]
に追記して solve
コマンドを叩くことでロックファイルが更新され,パッケージの実装も必要に応じて取得されます.
calc
という簡単なパッケージをつくりたいとします.以下のようにコンフィグファイル satysfi.yaml
とソースファイル src/calc.satyh
を配置します:
calc/
├── satysfi.yaml
└── src/
└── calc.satyh
language: "0.1.0"
package: "calc"
version: "0.0.1"
contents:
type: "library"
source_directories:
- "./src"
main_module: "Calc"
dependencies:
- name: "stdlib"
requirements: [ "0.0.1" ]
use open package Stdlib
module Calc :> sig
val sum : list int -> int
end = struct
val sum ns = List.fold-left ( + ) 0 ns
end
コンフィグファイルの dependencies: …
に依存パッケージとそのバージョン制約を列挙します.ここでは stdlib
パッケージの 0.0.1
またはその後方互換な後継バージョンを使うことを指示しています.実際,ソースファイル中では use open package Stdlib
で stdlib
を使用しており,List
モジュールが Stdlib
モジュールのメンバです.
ライブラリの場合も以下のように solve
コマンドで処理します(引数は satysfi.yaml
のあるディレクトリ):
$ satysfi solve .
これで処理すると,やはりロックファイル package.satysfi-lock
が生成されます:
calc/
├── satysfi.yaml
├── package.satysfi-lock
└── src/
└── calc.satyh
パッケージも以下でビルドすることができます(特に生成物はないため,型検査されるだけです):
$ satysfi build .
- モジュール (module): 実装の詳細を外部に漏らさないための抽象化の単位.有限個のメンバ (member) と呼ばれる内容からなり,各メンバは函数だったり,型だったり,或いは入れ子のモジュールだったりする.モジュールはメンバのうち一部の存在を非公開にしたり,型のメンバの定義を抽象化して外部に提供したりする.また,異なるモジュールに属するプログラムの間での名前空間の切り分けも担う.
- パッケージ (package): リリースの単位.特にインターフェイスの互換性を制御するカプセル化の単位であり,同時に変更されるモジュールが集まっている.また,やはり異なるパッケージに属するプログラムの間での名前空間の切り分けも担う.
モジュールシステム自体はML系言語のものとほぼ同一です.より具体的にはF-ing Modulesに準拠したものを採用する予定で,2022年12月2日現在で既に dev-0-1-0
ブランチにてほぼ実装済みです.
細かい言語設計の話としては,ライブラリの1ファイルがちょうど1つのモジュールの束縛であるように制限されます.モジュールは入れ子にできるのでどのモジュールも独立したファイルに書かれねばならないわけではありませんが,各ファイルの内容は1つのモジュールの束縛になっていなければならない,ということです.
具体的には,各ファイルは基本的に以下の
他のファイルのモジュールに依存する場合は
-
$\mathbf{use}\ X\ \mathbf{of}\ \mathit{string}$ - 相対パスでモジュール
$X$ に依存することを表す. - 文書ファイル,および文書ファイルから
$\mathbf{use}\ X\ \mathbf{of}\ \mathit{string}$ で読み込まれているファイルを追って再帰的に辿り着くファイル(=ローカルファイル)でのみ使える.
- 相対パスでモジュール
-
$\mathbf{use}\ X$ - 同一パッケージのファイルモジュール
$X$ に依存することを表す. - 文書ファイルやローカルファイルでは使えない.
- 同一パッケージのファイルモジュール
-
$\mathbf{use}\ \mathbf{package}\ X$ - 他のパッケージ
$X$ に依存することを表す. -
$X$ はメインモジュールの名前(メインモジュールについては後述).
- 他のパッケージ
ファイル名は,そのファイルが束縛しているモジュール名に拡張子をつけたものでなければなりません.
なお,従来のv0.0.zでは “明示的に @require:
や @import:
では依存を示していないものの既に読み込まれている” ファイルの中で定義された値や型が見えてしまう仕組みでしたが,v0.1.zではそうした “暗黙のバイパス” はなくなり,依存することを
パッケージは前述のとおり複数のモジュールからなるリリースの単位です.
各パッケージはメインモジュール (main module) と呼ばれるただひとつのモジュールだけをパッケージ外に公開します.例えば,標準ライブラリを stdlib
という1つのパッケージにするなら,Stdlib
というメインモジュールがあり,Stdlib.satyh
は以下のような内容をもつ,という具合です:
use Option
use List
...
module Stdlib :> sig
module Option : sig
val get-or-else 'a : 'a -> option 'a -> 'a
...
end
module List : sig
val map 'a 'b : ('a -> 'b) -> list 'a -> list 'b
...
end
...
end = struct
module Option = Option
module List = List
...
end
Option
や List
などは入れ子のモジュールとして外部からアクセスできるようになります(つまり Stdlib.Option
や Stdlib.List
という形で見え,必要に応じて open Stdlib
すれば単に Option
や List
として使える).また,これらの実装は Option.satyh
や List.satyh
といった stdlib
パッケージ内の別ファイルによって与えられており,それが Stdlib.satyh
の先頭の require
で読み込まれている,というわけです.
メインモジュールの名前は,パッケージ名をケバブケースからアッパーキャメルケースに変換したものであることが強く推奨されます.
各パッケージには satysfi.yaml
というコンフィギュレーションファイル (configuration file) を配置します.これは以下のような形式をとり,ユーザが書きます:
// コンフィギュレーションファイル
Config ::= {
"language": String, //期待するSATySFiのバージョン
"name": String, //パッケージ名
"version": String, //パッケージのsemver
"author": Array[String], //開発者名のリスト
...
"contents": Contents, //パッケージの内容
}
Contents ::= LibraryPackage | FontPackage | DocumentPackage | ...
LibraryPackage ::= {
"type": "library",
"source_directories": Array[String], //ソースファイルを格納したディレクトリへの相対パスのリスト
"test_directories": Array[String], //テストファイルを格納したディレクトリへの相対パスのリスト
"main_module": String, //メインモジュールの名前
"dependencies": Array[Dependency], //依存パッケージの指定
"test_dependencies": Array[Dependency], //テストでのみ依存するパッケージの指定
...
}
DocumentPackage ::= {
"type": "document",
"source_files": Array[String], //ソースファイルの相対パス
"dependencies": Array[Dependency], //依存パッケージの指定
...
}
//依存パッケージの指定
Dependency ::= {
"name": String, //依存パッケージ名
"requirement": String, //バージョン制約の記述
}
FontPackage ::= {
"type": "font",
"elements:" Array[FontFile], //パッケージに属するフォントファイルの一覧
...
}
FontFile ::= {
"path": RelativePath, //フォントファイルへの相対パス.例: "./fonts/foo.otf", "./fonts/foo.ttc"
"math": Bool, //数式フォントとして使うか否か.省略可,デフォルトでは false
"contents" : OpentypeSingle | OpentypeCollection,
}
OpentypeSingle ::= {
"type": "opentype_single",
"name": String, //SATySFiで使用する場合のフォント名
}
OpentypeCollection ::= {
"type": "opentype_collection",
"names": Array[String], //SATySFiで使用する場合の,各要素のフォント名
}
これは現状の Satyristes
ファイルに相当します(あくまでも大枠・暫定であり,Satyristes
ファイルなどを参考として改良すべき箇所があるかもしれません).
文書ファイルやパッケージをビルドするために,依存する各パッケージのバージョンが固定されたら,以下の形式のいわゆるロックファイル satysfi.lock.yaml
が文書ファイルやパッケージ直下に出力されます.SATySFiはこのロックファイルを読んでビルドします.
// ロックファイル
LockConfig ::= {
"language": String, //期待するSATySFiのバージョン
"checksum": String, //もとになった satysfi.yaml のチェックサム.更新の検査に使う
"locks": Array[Lock], //ロックされたパッケージ群.間接的なものも含めて依存するものが列挙される
}
// ロックされたパッケージ
Lock ::= {
"name": LockName, //ロックされたパッケージの名称.例: "enumitem.2.0.0"
"dependencies": Array[LockName], //依存する他のロックされたパッケージたち
"location": LockLocation, //ロックされたパッケージの場所
}
// ロックされたパッケージを一意的に識別するための名前
LockName ::= String
// ロックされたパッケージの場所
LockLocation ::= LocalLocation | GlobalLocation
// ロックファイルからの相対パスでの指定
LocalLocation ::= {
"type": "local",
"path": RelativePath,
}
// LIBROOTからの相対パスでの指定
GlobalLocation ::= {
"type": "global",
"path": RelativePath,
}
ここに掲げたパッケージシステムの設計は,ほぼ同様のものを既にSesterlという拙作の言語で実装し,Rebar3というErlang向けのビルドシステムと組み合わせて実用しています.この場合,SesterlがSesterl向けのコンフィグレーションファイル sesterl.yaml
をもとにRebar3向けのコンフィギュレーションファイル rebar.config
を生成し,Rebar3がそれを使って依存ライブラリのソースコードを取得するという形態をとっています.
本稿では,SATySFi v0.1.zでの導入を検討しているパッケージシステムの大枠の設計を記述しました.これによりSATySFi本体がパッケージという形式でリリースの単位を扱うようになり,名前空間の分離なども明瞭になります.
特に依存パッケージの取得・配置処理などSATySFi本体が今のところ関知しない点についてもSATySFiが直接見るコンフィギュレーションファイルに記述するか否かについては議論が分かれそうに思いますが,プロトタイプ実装をつくって開発者やユーザの皆さんからご意見を伺ったりしたいと思います.
- トップページ
- The SATySFibook Web公開版 第1版
- Wiki
- 目的別パッケージ一覧
- コマンドライン書式
- SATySFiコマンド一覧
- Satyrographos(パッケージマネージャ)
- 新しい言語機能の紹介
- 言語機能の構想