Idrisdocの使い方

このエントリはIdris Advent Calendar 2020の15日目の記事です。

κeenです。今回はIdrisのドキュメントコメントとそれを使ったドキュメント生成ツールの使い方を紹介します。

ベースになるのは 公式リファレンスです。簡素な説明しかないので少し補いながら説明していきます。

ドキュメントコメント

トップレベルの宣言の前に ||| ではじまる行を置くことでドキュメントコメントになります。

例えば以下のように書けます。

||| モジュールにドキュメントを書ける
module Docs

書いたドキュメントはいくつか用途がありますが、例えばREPLから参照することができます。

Idris> :doc Docs
Module Docs:
    モジュールにドキュメントを書ける

ドキュメントコメントは連続して書けば複数行でも書けます。 また、Javadocと同様の構文、 @ パラメータ名 説明 でパラメータに説明を加えることもできます(要名前つきパラメータ)。

import Data.Vect

||| ベクタを連結する
||| @ a ベクタの内容
||| @ xs 先頭のベクタ(再帰引数)
||| @ ys 次のベクタ(場合分けに使われない)
appendV : (xs : Vect n a) -> (ys : Vect m a) -> Vect (add n m) a
appendV []      ys = ys
appendV (x::xs) ys = x :: appendV xs ys

これはREPLだと以下のように表示されます。

Idris> :doc appendV
Docs.appendV : (xs : Vect n a) -> (ys : Vect m a) -> Vect (add n m) a
    ベクタを連結する
    Arguments:
        (implicit) a : Type  -- ベクタの内容
        
        xs : Vect n a  -- 先頭のベクタ(再帰引数)
        
        ys : Vect m a  -- 次のベクタ(場合分けに使われない)
        
    The function is: Total & public export

ドキュメントコメントではマークダウン記法が使えます。

||| 数値を足す
|||
||| 足し算はめちゃくちゃすごいよね。この段落はOverviewに含まれない。
||| 後続の行も1つの段落に入る。
|||
||| ドキュメント内に表示されるコードサンプルを書くこともできる
||| ```idris example
||| add 4 5
||| ```
|||
||| リスト記法:
||| * はい
||| * いいえ
||| * コードの `add` や **太字** なども使える
||| @ n は再帰パラメータ
||| @ m は再帰パラメータでない
add : (n, m : Nat) -> Nat
add Z     m = m
add (S n) m = S (add n m)

これをREPLで表示すると以下のようになります。

Idris> :doc add
Docs.add : (n : Nat) -> (m : Nat) -> Nat
    数値を足す
    
    足し算はめちゃくちゃすごいよね。この段落はOverviewに含まれない。 後続の行も1つの段落に入る。
    
    ドキュメント内に表示されるコードサンプルを書くこともできる
    
        > add 4 5
        9
    
    リスト記法:
    
    * はい
    * いいえ
    * コードの add や 太字 なども使える
    Arguments:
        n : Nat  -- は再帰パラメータ
        
        m : Nat  -- は再帰パラメータでない
        
    The function is: Total & public export

テキストで貼ってしまうと分かりづらいですが、REPLを表示している上ではコードのシンタックスハイライトや等幅、太字なども処理されています。

マークダウンエンジンはcheapskateが使われているようです。画像へのリンクなどもサポートしていますが、ターミナルでは表示できません。後述のidrisdocによるHTML生成などではできるようです。

Overviewという用語が登場していますが、これは :apropossearch で検索したときに表示されます。

Idris> :apropos add
...

Docs.add : Nat -> Nat -> Nat
数値を足す

...

ドキュメントの最初の1行だけが表示されていますね。

さて、ドキュメントを書けるのはトップレベルの宣言以外にもデータ型のコンストラクタなどもあります。

||| シンプルなデータ型
data Ty =
  ||| Unit型
  UNIT |
  ||| 関数型
  ARR Ty Ty

GADTを使うと名前付きパラメータが使えるのでコンストラクタの引数のドキュメントも書けるようになります。

||| 型文脈での場所を指す
data Elem : Vect n Ty -> Ty -> Type where
  Here : {ts : Vect n Ty} -> Docs.Elem (t::ts) t
  There : {ts : Vect n Ty} -> Docs.Elem ts t -> Docs.Elem (t'::ts) t

||| もうちょっと面白いデータ型
||| @ n 自由変数の数
||| @ ctxt 自由変数の型文脈
||| @ ty 項の型
data Term : (ctxt : Vect n Ty) -> (ty : Ty) -> Type where

  ||| Unit型のコンストラクタ
  ||| もっとコメント
  ||| @ ctxt 型文脈
  UnitCon : {ctxt : Vect n Ty} -> Term ctxt UNIT

  ||| 関数適用
  ||| @ f 適用する関数
  ||| @ x 引数
  App : {ctxt : Vect n Ty} -> (f : Term ctxt (ARR t1 t2)) -> (x : Term ctxt t1) -> Term ctxt t2

  ||| ラムダ抽象
  ||| @ body 関数本体
  Lam : {ctxt : Vect n Ty} -> (body : Term (t1::ctxt) t2) -> Term ctxt (ARR t1 t2)

  ||| 変数
  ||| @ i de Bruijn インデックス
  Var : {ctxt : Vect n Ty} -> (i : Elem ctxt t) -> Term ctxt t

もちろん、レコードにもドキュメントコメントが書けます。

||| フィールドやコンストラクタを含めてレコードにもドキュメントが書けるよ
record Yummy where
  ||| Yummyを作る
  constructor MkYummy
  ||| 食べるもの
  food : String

それぞれ以下のように表示されます。

Idris> :doc Term
Data type Docs.Term : (ctxt : Vect n Ty) -> (ty : Ty) -> Type
    もうちょっと面白いデータ型
    Arguments:
        (implicit) n : Nat  -- 自由変数の数
        
        ctxt : Vect n Ty  -- 自由変数の型文脈
        
        ty : Ty  -- 項の型
        
    The function is: public export
Constructors:
    UnitCon : Term ctxt UNIT
        Unit型のコンストラクタ もっとコメント
        Arguments:
            (implicit) ctxt : Vect n Ty  -- 型文脈
            
        The function is: public export
    App : (f : Term ctxt (ARR t1 t2)) -> (x : Term ctxt t1) -> Term ctxt t2
        関数適用
        Arguments:
            f : Term ctxt (ARR t1 t2)  -- 適用する関数
            
            x : Term ctxt t1  -- 引数
            
        The function is: public export
    Lam : (body : Term (t1 :: ctxt) t2) -> Term ctxt (ARR t1 t2)
        ラムダ抽象
        Arguments:
            body : Term (t1 :: ctxt) t2  -- 関数本体
            
        The function is: public export
    Var : (i : Elem ctxt t) -> Term ctxt t
        変数
        Arguments:
            i : Elem ctxt t  -- de Bruijn インデックス
            
        The function is: public export
Idris> :doc Yummy
Record Yummy
    フィールドやコンストラクタを含めてレコードにもドキュメントが書けるよ

Constructor:
    MkYummy : (food : String) -> Yummy
        Yummyを作る
        Arguments:
            food : String  -- 食べるもの
            
        The function is: public export
Projections:
    food : (rec : Yummy) -> String
        食べるもの
        
        The function is: public export

もうちょっと凝った文法があるかなと思って実装を読んだんですがマークダウンエンジンに丸投げだったのでマークダウン記法と @ パラメータ名 説明 の文法以外は特になさそうでした。

Idrisdoc

次にドキュメントコメントからHTMLを生成する方法を解説します。

REPLから

:mkdoc コマンドでHTMLを生成できます。その際、 doc/ 以下に生成します。

例えば Docs.idr を読み込みつつREPLを開き、 :mkdoc Docs でHTMLを生成するとこうなります。

$ idris Docs.idr
     ____    __     _
    /  _/___/ /____(_)____
    / // __  / ___/ / ___/     Version 1.3.3
  _/ // /_/ / /  / (__  )      https://www.idris-lang.org/
 /___/\__,_/_/  /_/____/       Type :? for help

Idris is free software with ABSOLUTELY NO WARRANTY.
For details type :warranty.
Idris> :mkdoc Docs
IdrisDoc generated
*Docs>
Bye bye
$ ls doc
IdrisDoc  docs  index.html  styles.css

これで生成されたドキュメントを見るとpreludeなどに混じってDocs モジュールのドキュメントがあるのが確認できます。

コマンドラインから

残念ながら単一のファイルのドキュメントを生成するコマンドはなさそうでした (コンパイラの実装を見ましたが、 --mkdoc しかなかったです)。しかしパッケージのドキュメントを生成することはできます。

さっきの Docs をパッケージにしましょう。サクッと以下のようなディレクトリを作ります。

$ tree
.
├── Docs.ipkg
└── src
    └── Docs.idr

そして Docs.ipkg には以下の記述をします。

package Docs

version = "0.1.0"
author = your name

sourcedir = src
modules = Docs

あとは idris --mkdoc Docs.ipkg を打つだけです。

$ idris --mkdoc Docs.ipkg
Type checking src/Docs.idr
$ ls
Docs.ipkg  docs_doc  src

こっちは 小文字のモジュール名_doc にドキュメントが生成されます。 内容はREPLのものと変わりません。

インストールとdocdir

ちょっと何に使うのか分かってないのですが、ドキュメントをインストールすることもできます。 --installdoc IPKG のコマンドです。

$ idris --installdoc Docs.ipkg

これは idris --docdir で表示される場所にドキュメントをインストールします。

$ ls $(idris --docdir)
base  contrib  docs  effects  prelude  pruviloj

ls の結果の中に docs がいますね。 …ですがこれをどうしたらいいのかよく分かってません。Idris側ではこれらを表示するコマンドやサーバを立てる方法は用意してないようです。

強いていうなら以下のようにワンライナーサーバを立てるくらいでしょうか。

$ ruby -run  -e httpd $(idris --docdir)

まとめ

Idrisdocの記法と生成ツールの使い方を紹介しました。 Idrisdocにはマークダウンと引数の説明の文法がありました。 生成ツールにはREPLから起動して名前空間を指定する方法とCLIから起動してパッケージのドキュメントを生成する方法がありました。

Written by κeen