SATySFi v0.1.0 のリリース後、SLyDIFi がどう発展するかについて
はじめに
SLyDIFi は SATySFi でスライドを作成するためのクラスファイル(に相当するパッケージ)です。
Satyrographos 経由でインストールでき、SATySFi の文法さえ知っていれば、以下のようなマークアップで誰でも簡単にスライドを作ることができます。
マークアップ
% SLyDIFi と直接関係のないインポートや関数定義は省略
@require: class-slydifi/theme/arctic
document '<
% +make-title でタイトル用のフレームを作れる
+make-title(|
title = {|\SLyDIFi; で|らくらくスライド作成|};
author = {|monaqa|GitHub: \link(`https://github.com/monaqa`);|};
date = {| 2021年6月6日 |};
|);
% +section で複数のフレームをセクションにまとめることができる
+section{|セクションスライドの|具体例|}<
% スライドのページ1枚(フレーム)は +frame で作る
+frame{フレーム作成 in \SLyDIFi;}<
+listing{
* フレーム:スライド資料の1ページ1ページに値するもの
* \SLyDIFi; では3種類のフレームを区別する
** 見出し:スライド全体の題目、発表者名などを載せるフレーム
** セクション見出し:セクションのタイトルを載せる
** 本文:通常のフレーム
}
>
+frame{テキストの記述}<
+p{以下のようなコマンドを用いてテキストを記述できる。}
+listing{
* `+p{}`: 段落
* `+listing{}`: 番号のない箇条書き
* `+enumerate{}`: 番号付きの箇条書き
}
+p{さらに、インラインテキストの中では以下のマークアップが使える。}
+listing{
* `\emph{}`: \emph{強調}
* `\text-color(){}`: \text-color(Color.of-css `#42883B`){文字色変更}
}
>
>
>
より詳しいコードと出力の例は以下を参照してください。
SLyDIFi のインストール方法や使い方については去年のアドベントカレンダーで投稿した解説記事をご覧ください。
世の中には様々な見た目のスライドがあります。仮に同一の内容を扱うスライドであっても、文字の書体や大きさ、配色などといったレイアウト(テーマ)によって大きく印象が変わるものです。自分の目的や好みに合ったテーマを選びたい、さらに既存のテーマをより自分好みにカスタマイズしたい、これらはスライドを作成するなら自然に生まれる要望といえるでしょう。
本記事では、SATySFi v0.1.0 のリリース後に SLyDIFi がどのような形でスライドテーマの枠組みを提供するか、その構想をまとめます。
SATySFi v0.0.x における SLyDIFi のテーマの枠組み
SATySFi の現行の最新バージョンは v0.0.8 であり、互換性のない大規模な変更である v0.1.0 が来年頃に正式リリース予定となっています。ここでは前者 (0.0.8) を「現行 SATySFi」、後者を(0.1.0)「新 SATySFi」と呼ぶこととしましょう。
実は、現行 SATySFi で動く SLyDIFi(現行 SLyDIFi)にもすでにスライドテーマを管理する枠組みが備わっています。実際、以下のディレクトリには現行 SATySFi でコンパイル可能ないくつかのスライド例があります。文書ファイル (*.saty
) の中身はほぼ同一でも、インポートするテーマを変えることで出力結果 (*.pdf
) のレイアウトが大きく異なることが分かります。
現在は以下のような枠組みとなっています。
- スライドテーマはパッケージとして提供する(テーマパッケージ)。
- class-slydifi パッケージにデフォルトでいくつか入っている。たとえば
class-slydifi/theme/plain
パッケージを使えばplain
テーマが使える。 - 自分で作ることもできる。
- class-slydifi パッケージにデフォルトでいくつか入っている。たとえば
- 各テーマパッケージは設定項目 (config 型) を定義する。設定項目のフィールドはテーマにより異なる。
-
たとえば
plain
テーマであれば以下のような感じ。type plain-config = (| font-normal : context -> context; % 通常のテキストのフォント設定 font-emph : context -> context; % 強調テキストのフォント設定 color-bg : color; % 背景色 ... |)
-
テーマパッケージは config 型のデフォルト値を提供する。
-
+set-config
コマンド等を本文中で用いると、そのテーマを用いるユーザ自身の手で設定値を変更できる。
-
- テーマパッケージはスライドのマークアップに必要となる以下のようなコマンドを提供する。
-
+make-title
: タイトルスライド -
+frame
: 1枚のフレーム -
\emph
: 強調表示 - その他、テーマにより追加コマンドを提供することもある
-
つまり、ユーザはテーマを選び、さらに各テーマ内の設定をいじることでカスタマイズできるようにしたのです。このような構造にすることで、以下の恩恵を受けることができます。
- ライトユーザにとっては、複雑な設定をせずとも複数の選択肢からテーマを選べる。
- ヘビーユーザにとっては、設定をカスタマイズすることで自分好みのレイアウトを実現しやすい。
- 定義されているコマンドの名前や型を揃えることで、マークアップがある程度共通化される。
- コマンドの拡張を許すことで、リッチなテーマと簡素なテーマを両立できる。
一方で、以下のような問題点があります。
- 「各テーマがどのような関数やコマンドを提供する必要があるか、設定値はどのように渡す必要があるか」について、現状不文律となってしまっている。
- スライドテーマは「自分で作ることもできる」と書いたが、従来のスライドテーマのシグニチャを真似て作る必要があり、面倒。
- SLyDIFi のテーマを作成する上で何が必須の関数なのかすぐに判断できない。
- テーマ作成者用にドキュメントを書け、というのはその通りだが、型システムで「実装しなければならないコマンド」などの条件を付けられると便利。
- 各テーマの設定値 (config) を変更する方法が分かりにくい。
- 実装の都合上、可変参照を用いて config を取り回すような形になっており、関数型言語のパラダイムと相性が良くない。
これらは現行 SLyDIFi では根本的な解決が難しい課題でした。
新 SLyDIFi のテーマの枠組み
上で述べた課題は、 v0.1.0 で大きく解決されることとなります。鍵は 新 SATySFi で新たに取り入れられることとなった f-ing modules というモジュールシステムにあります。
f-ing module の採用により、新 SATySFi ではファンクタが定義できるようになります。ファンクタとは「モジュールを受け取りモジュールを返す関数のようなもの」であり、「スライドテーマ」を抽象化するのに重要な役割を果たします。f-ing module を採用した SATySFi の具体的な解説は別の記事に譲りますので、SATySFi 作者の gfn さんによる以下のスライドなどを参照してください。
新 SLyDIFi においても「ユーザはテーマを選び、さらに各テーマ内の設定をいじることでカスタマイズできるようにする」という方針は変わりません。「テーマ」や「設定」といった要素をより適切に抽象化できるようにした点がポイントです。
設定からスライド生成用のクラスファイルが作られるまでの、大まかな流れは以下の図のようになります。
大まかなモジュールや機能の流れ
テーマの枠組みを構成する3要素
SLyDIFi を用いてスライドを作成するとき、大きく分けて3種類の役割を持つモジュールやファイルが存在することになります。
-
Slydifi パッケージ: 全てのテーマの共通処理を記述するパッケージ
- SLyDIFi 本体である
class-slydifi
パッケージに含まれています。 - 以下の役割を果たします。
- テーマシグネチャを定義。
- テーマからクラスファイルを作成するファンクタを実装。
- SLyDIFi 本体である
-
各テーマ生成ファイル: SLyDIFi のテーマを記述するファイル・モジュール群
- デフォルトのテーマは
class-slydifi
パッケージに含まれていますが、同じ要領で第三者が自由に定義できます。 - 以下の役割を果たします。
- テーマごとに設定シグネチャを定義。
- デフォルトの設定を実装。
- 設定からクラスファイルを作成するファンクタを実装。
- デフォルトのテーマは
-
文書ファイル: テーマを用いて実際に文書を記述するファイル
- 当然、文書を書くユーザが作成します。
- 以下の役割を果たします。
- テーマを選び、そのテーマの Config を実装(省略可能)。
- クラスファイルの I/F に沿って文書を記述。
これらが果たすそれぞれの役割を見てみましょう。
Slydifi モジュール
Slydifi モジュールの責任範囲
Slydifi モジュールの重要な仕事は2つあり、1つは Theme
シグネチャを定義することです。現時点で Theme
シグネチャは以下のように定義されています。
signature Theme = sig
type title-content :: o
% レイアウト
val layout: layout
val init-ctxf: context -> context
val frame-normal: frame (| title: inline-text, body: block-text |)
val frame-title: frame title-content
val \emph: inline [?(cond: condition) inline-text]
end
このコードの意味を日本語で述べるなら、以下のようになります。
- 全てのテーマには title-content という型が必要。
-
title-content はタイトルフレームに載せるコンテンツを表す型。
- たとえば通常のスライドなら「スライドのタイトル」「発表者名」「日付」を表示することが多いが、その場合は以下のようなレコード型がふさわしい。
type title-content = (| title: inline-text, author: inline-text, date: inline-text, |)
- もし学術発表用のテーマなら、「所属 (affiliation)」フィールドや「会議名」フィールドなどを追加することも考えられる。
- たとえば通常のスライドなら「スライドのタイトル」「発表者名」「日付」を表示することが多いが、その場合は以下のようなレコード型がふさわしい。
-
title-content はタイトルフレームに載せるコンテンツを表す型。
- 全てのテーマには
layout
という名前の値が必要。-
layout
は layout 型を持ち、紙面サイズや本文のテキスト幅など、テーマに関わらず必須となる設定項目。type layout = (| paper-width: length, paper-height: length, text-width: length, text-height: length, text-horizontal-margin: length, text-vertical-margin: length, |)
-
- 全てのテーマには
frame-normal
、frame-title
という frame 型の値が必要。-
frame 型とはフレームを作成する方法を記述する関数型。
- コンテンツとテキスト処理文脈が与えられたとき、「本文」と「背面に配置するグラフィックス」「前面に配置するグラフィックス」を返す関数。
-
frame-normal
は通常のフレームを作成する方法を表す。 -
frame-title
はタイトルフレームを作成する方法を表す。
-
frame 型とはフレームを作成する方法を記述する関数型。
- 全てのテーマには
\emph
コマンドが必要。- インラインテキストを強調する方法を示すもの。
Slydifi モジュールが担うもう1つの重要な仕事は Slydifi.Make
ファンクタです。これは Theme
シグニチャを有するモジュールを受け取って、SLyDIFi のクラスファイル(に相当するモジュール)を返すものであり、以下のような構造となっています。
module Make = fun (T: Theme) -> struct
% T で定義されたレイアウト設定などを用いて `document` 関数を定義
val document bt = ...
% T で定義された関数などを用いて `+frame` コマンドを定義
val block +frame ?(n-layer = n-layer-opt) it-title bt-body = ...
% T で定義された関数などを用いて `+make-title` コマンドを定義
val block +make-title content = ...
% T に入っているコマンド群が使えるようにする
include T
end
Theme
シグニチャを持つモジュール T
が与えられた下で、クラスファイルとして最低限必要な関数とコマンドを生成する方法を定義しています。T
にはクラスファイルとして成立させるのに必要な document
や +frame
といったコマンドが定義されていませんが、これらは T
の情報を下に Slydifi.Make
内で生成してくれるため問題ないという塩梅です。
このように各テーマで共通の処理をまとめることで、テーマを開発する人は document
関数などを直接定義する必要がなく、テーマ本体のロジックに集中できるのです。
テーマファイル
各テーマの責任範囲
テーマファイルは、テーマの設定方法を定義し、実際にユーザが使えるようなクラスファイルを提供するところまでを責務としています。具体的には以下の役割を果たします。
-
Config
シグニチャを定義 -
Config
シグニチャを満たすモジュール(デフォルトの設定値)を定義 -
Make
シグニチャを定義- 内部で
Config
からTheme
モジュールを生成し、Slydifi.Make
を用いてクラスファイルを生成
- 内部で
テーマファイルの実装の一つである Plain
モジュールを例に挙げてみましょう。Plain
モジュールのコードは以下のようになっています。
module Plain = struct
signature Font = sig
val normal: context -> context
val emph: context -> context
...
end
signature Color = sig
val fg: color
val bg: color
end
signature Length = sig
val margin-bot-frame-title: length
end
signature Config = sig
module Font: Font
module Color: Color
module Length: Length
end
% デフォルトの設定値の定義
module DefaultConfig :> Config = struct
module Font = struct
val normal = ...
val emph = ...
end
module Color = struct
val fg = Color.black
val bg = Color.gray 0.8
end
module Length = struct
val margin-bot-frame-title = 20pt
end
end
module Make = fun (C: Config) -> struct
module Theme = struct
type title-content = (| title: inline-text |)
val layout = (| ... |)
val init-ctxf ctx = ctx |> C.Font.normal
val frame-title ctx content = ...
val frame-normal ctx content = ...
val inline ctx \emph ?(cond = cond-opt) it = ...
end
include Slydifi.Make Theme
% Plain モジュール固有のコマンド
val block +frame-dummy it-title bt-body =
let () = Slydifi.increment-page-num 2 in
'<
+genframe(1)(frame-normal)(|title = it-title, body = bt-body|);
>
end
% デフォルト設定を用いて生成したクラスファイル
module DefaultClass = Make DefaultConfig
end
ほとんど上で説明した通りの実装となっています。
なお、Config
モジュールは内部で更に Font
/Color
/Length
の3つのサブモジュールに分かれています。このように構造を細分化することで、ユーザにとって設定の見通しが良くなります。
文書ファイル
ユーザから見たテーマ
文書ファイルの仕事は「テーマファイルを選び、そのテーマファイルのマークアップに従って組版する」ことです。仰々しく書いていますが、やることは通常の文書ファイルと同様、クラスファイルを読み込み、定義されたコマンドで組版を行うだけです。
このとき、Plain
テーマを用いるユーザは2通りの選択をすることができます。
- ライトユーザ:
Plain.DefaultClass
をそのまま用いる。- モジュールを定義する必要が一切ないため、楽に書き始めることができる。
- ヘビーユーザ:
Plain.Config
シグニチャを持つモジュールを生成し、Plain.Make
で新しいクラスファイルを作る。- 決められたフィールドを埋めるだけで、自由度の高い設定ができる。
- ユーザが行った設定により生成されるクラスファイルそのものが変わるため、可変参照などを使う必要もない。
現状と今後の展望
現在は grammar-v0_1_0
ブランチにて新 SLyDIFi の実装を進めています。ここで紹介したコードも、このブランチの実装から抜粋したものです。
すでに概念実証は済んでおり最低限のスライドは問題なく生成できたため、ひとまず上記の方針で実装を進めようと思います。
ただし今の所、それ以上の実装は手が止まっている状況です。というのも元々の SLyDIFi の実装が satysfi-base に強く依存しており、 v0.1.0 にはまだ satysfi-base の実装がないからです。satysfi-base の機能が揃うのを待ってから SLyDIFi の実装を進めたいと思います。
12/9 bd_gfngfn
satysfi-baseをSATySFi v0.1.0に移植してみる
この記事が待ち遠しいですね!
終わりに
f-ing modules の話を最初に SATySFiConf で聞いたとき、なんて夢の広がる機能なんだ、と思いました。実際にSLyDIFi に適用してみて、確かな手応えを感じています。スライドテーマに限らず、細かいカスタマイズが求められる組版と全体的に相性が良いように思いました。
SATySFi v0.1.0 には他にも様々な機能が搭載されます。新機能をフルで使いこなせるように今後もキャッチアップを続けたいと思います。正式にリリースされた暁には、皆さんも是非新しい方の SATySFi と SLyDIFi を使ってみてください!
Discussion