これは「SATySFi Advent Calendar 2019」の24日目の記事です。
23日目はbd_gfngfnさんでした。25日目はpuripuri2100です。
はじめに
SATySFiで機能を拡張したり新たな機能を追加したりするときに用いるのが「パッケージ」です。
実態としてはlet-inline
やlet-block
で定義されたコマンド、let
やlet-rec
で定義された関数などです。
利用者側がパッケージを@require
や@import
で読み込むだけで、そこで定義されているコマンドや関数を使えるようになるという便利機能で、SATySFiを使っている人間ほぼ全員がお世話になっていることでしょう。
さて、このパッケージはとても簡単に作ることができるのでここで解説してみようと思います。
コマンドや関数をパッケージ化することでメンテナンスしやすくなったり、便利機能を使いまわすことができたり、車輪の再発明を防いだりすることができるので、良いコマンドや関数を作ったらパッケージ化していくようにしましょう。
パッケージの配布方法についてはこのアドベントカレンダーの「素敵なライブラリをSatyrographosで配布しよう!」をお読みください。
ファイルを分ける
まず、機能が似ている関数やコマンド、目的が同じ関数やコマンドを別のファイルに分けましょう。これだけでもパッケージになります。
基本としてはそのままコピペするだけですが、注意点として依存する他のパッケージがある場合はそれを読み込む必要がありますし、元の文書のプリアンブルなどに依存する関数などが書いてある場合はそれもすべてコピペする必要があるということです。
ここで定義された関数などは、パッケージを読み込んだだけですぐに使えるようになります。
便利ですね。
しかし、これには欠点があります。
内部で関数などを定義するときだけに使われるものがトップレベルで定義されてしまい、場合によっては他のパッケージの関数などを覆い隠してしまい、衝突してしまうかもしれないのです。
ここで使う機能がモジュールです。
モジュールを使う
定義する関数などに名前空間を付ける機能がモジュールです。
モジュールは
module 〈名前〉 = struct
関数やコマンドの定義
end
という形で使います。
コンストラク名は大文字のアルファベットから始まるアルファベットと数字の組み合わせのものです(正規表現なら[A-Z][a-zA-Z0-9]*
となります)。
ここで定義された関数は〈モジュール名〉.〈関数名〉
のようにして使います。
たとえばこんな感じです。
module Module0 = struct
let hoge = 1
end
let fuga = Module0.hoge
一々モジュール名を書くのが面倒な時はopen 〈モジュール名〉
とすることで省略できるようになります。
効果を局所的にしたいときはopen 〈モジュール名〉 in
とするか〈モジュール名〉.(式)
とするかします。
シグネチャを使う
モジュールを使うと名前空間によって関数などが衝突するのを防げますが、内部で使うだけの関数も公開されてしまいます。
そこで、シグネチャという機能を使うことで後悔する関数やコマンドを限定することができます。
使い方は
module 〈名前〉:sig
公開する関数などの名前と型
end = struct
関数やコマンドの定義
end
です。関数はval f : 型
の形で、型はtype t
かtype t = 定義
の形で公開できます。
コマンドはval cmd : 型のリスト inline-cmdかblock-cmdの指定
かdirect cmd : 型 inline-cmdかblock-cmdの指定
でできます。direct
を使うと、トップレベルで定義したのと同じように、モジュール名を付けなくてもコマンドを使うことができるようになります。
例えばこんな感じです。
@require: stdja
module Module1 :sig
val hoge : int
direct \test : [string] inline-cmd
direct +test : [string] block-cmd
end = struct
let hoge = 1
let fuga = 2 %公開されない
let-inline \test str = embed-string str
let-block +test str = <+p{\test(str);}>
end
おわりに
短い記事なりましたが、モジュール機能やシグネチャを使って素敵なパッケージをたくさん作ってみてください。