Red Data Tools で RedAmber という Ruby で書かれたデータフレームライブラリを作っている @heronshoes です。RedAmber は、Ruby でデータフレームを扱うためのライブラリで、Python の pandas、R の data.frame または dplyr/tidyr がやるようなことを Apache Arrow を利用して Ruby でできるようにするためのライブラリです。RubyKaigi2023 では、「The Adventure of RedAmber - A data frame library in Ruby」というタイトルで3日目に発表しました。
大変ありがたいことに 2022 年度の Ruby アソシエーション開発助成に採択されまして、その期間中にはコードとドキュメントの整備にも取り組みました。そしてドキュメントを整備する中で、YARD の使い方について色々知らなかった事に触れることができましたので、ここで共有させていただきたいと思います。
RedAmber では、表の各列のデータを表すオブジェクト Vector の関数的なメソッド、例えば #mean
, #abs
, #>
などは Apache Arrow の C++で書かれた Compute Function を利用して #define_method
で動的に生成しています。
当初はそのように動的に生成されたメソッドに対して効率的にドキュメントを付加する方法がわからなかったのですが、最終的に下記の方法に辿り着きました。ここでは、Vector の要素に対してユニークな要素の数を数えるメソッド #count_distinct
を例にしてドキュメントを付与してみます。
クラスメソッドにしなくても動的にメソッドを定義することはできるので最初はそうしていたのですが、ドキュメント生成のためにクラスメソッドで書いた結果、コードの見通しも良くなったように感じます。
module RedAmber
class Vector
class << self
private
def define_unary_aggregation(function)
define_method(function) do |**options|
datum = exec_func_unary(function, options)
get_scalar(datum)
end
end
end
end
end
define_unary_aggregation(function)
は Arrow の Compute Function の名前を受け付けてそれを呼び出すメソッドを定義するためのメソッドです。これは、例えば #sum
や #mean
のような、引数がなく結果として一つのスカラーを返すメソッド(Aggregation メソッド) を定義するために使います。同類のクラスメソッドとして、#cumsum
や #abs
のような引数を取らずに結果を Vector で返すメソッドを定義するための define_unary_element_wise
や、 #>
や #+
のような引数を取って結果を Vector で返すようなメソッドを定義するための define_binary_element_wise
があります。
上で定義したクラスメソッドを使って、関数的なメソッドの定義を書いていきます。必要に応じて、エイリアスを定義します。
module RedAmber
class Vector
define_unary_aggregation :count_distinct
alias_method :count_uniq, :count_distinct
end
end
ここでは、Ruby らしい別名として #count_uniq
を用意してみました。
いよいよこれにドキュメントを付与していきます。ポイントを箇条書きにしてご説明します。
クラスメソッドで共通のドキュメントは @!macro[attach]
でクラスメソッドに付加します。
全部に共通ではないが適宜利用するマクロはインスタンスメソッドの上の方で定義します。
ここでは、mode
というオプションを使うメソッドに共通のドキュメントを定義しています。
メソッド固有のドキュメントはメソッド定義のすぐ上に書きます。
@!method
で引数とオプションを書きます。
メソッドの別名は alias_method で書きます。 実装上はクラスメソッド経由で定義することもできますが、このようにするとドキュメントで ‘Also known as:’ として正しく表示されます。
module RedAmber
class Vector
class << self
private
# @!macro [attach] define_unary_aggregation # 1
# [Unary aggregation function] Returns a scalar.
#
def define_unary_aggregation(function)
define_method(function) do |**options|
datum = exec_func_unary(function, options)
get_scalar(datum)
end
end
end
# @!macro count_options # 2
# @param mode [:only_valid, :only_null, :all]
# control count aggregate kernel behavior.
# - only_valid: count only non-nil values.
# - only_null: count only nil.
# - all: count both.
# Count the number of unique values. # 3
#
# @!method count_distinct(mode: :only_valid) # 4
# @macro count_options # 2
# @return [Integer]
# unique count of self.
# @example
# vector = Vector.new(1, 1.0, nil, nil, Float::NAN, Float::NAN)
# vector
#
# # =>
# #<RedAmber::Vector(:double, size=6):0x000000000000d390>
# [1.0, 1.0, nil, nil, NaN, NaN]
#
# # Float::NANs are counted as 1.
# vector.count_uniq # => 2
#
# # nils are counted as 1.
# vector.count_uniq(mode: :only_null) # => 1
#
# vector.count_uniq(mode: :all) # => 3
#
define_unary_aggregation :count_distinct
alias_method :count_uniq, :count_distinct # 5
end
この結果生成されたドキュメントは、 RedAmber YARD Vector#count_distinct にあります。
該当する YARD のドキュメントは YARD document / Tags にあります。クラスメソッドで定義するメソッドの種類が限られている場合は、$0, $1, $2, ...
などの変数を使って引数の説明を入れる方法も有効だと思います。今回の例では、定義されたメソッドで受け付けるオプションの種類が一種類ではないため、上のようなやり方に落ち着きました。
YARD のドキュメント には、css ファイルを使ってドキュメントのデザインをカスタマイズするテンプレートの例が書かれています。
RedAmber のドキュメントでは @example
を使ってコード例を多く表示させていますが、デフォルトの設定ではそれらはプロポーショナルフォントで表示されてしまいます。これを回避するために、テンプレートをカスタマイズする方法を使いました。
YARD テンプレートをカスタマイズする際には YARD 標準のテンプレートと同じディレクトリ構造の中に置く必要があります。
.yardopts
にカスタムテンプレートを置くパスを指定しました。
--template-path doc/yard-templates
カスタマイズした下記のような css を doc/yard-templates/default/fulldoc/html/css/common.css
に置きました。
/* Use monospace font for code */
code {
font-family: "Courier New", Consolas, monospace;
}
その結果、下記のように表示できました。
このカスタマイズは YARD 本体に取り込んでいただけるよう提案していきたいと思っています。
YARD をちゃんと書くのは初めての経験でしたが、Ruby Association Grant の助成適用ということもあり、全てのメソッドのドキュメント化を達成することができました。大変でしたがドキュメントにまとめることでライブラリのメソッド設計を整理することもできて良かったと感じています。お気づきの点がございましたらご指摘をいただけると嬉しいです。Ruby によるデータ処理に興味がある方、Ruby で新しいことに取り組んでみたい方は、Red Data Tools に来てみて下さい。Matrix 上のチャットルーム でお待ちしています。
heronshoes (Hirokazu SUZUKI)。 ex-Twitter: @heronshoes, GitHub: @heronshoes, 広島県福山市在住の Ruby 愛好家。好きなメソッドは tally、シングルクォート派。コーヒーとクラフトビールと MINI も好き。今週のコーヒーを GitHub のステータスに表示しています。