S7 について調べたことをメモる
公式ウェブサイト
概要については、とりあえず Qiita のこの記事を読めばよさそう。
なぜ S7 をつくるのか?
S3 のダメなところ
S3 is very informal, meaning that there’s no formal definition of a class. This makes it impossible to know exactly which properties an object should or could possess,
S3 だと、そのクラスのオブジェクトがどのようなプロパティを持っているのかわからない(例えば、x$foo
みたいなコードを書きたいけど、foo
が必ず存在する保証がない、みたいなことだと思う)
あと、バリデーションもかけられないので、数字だと思ってたら文字列が入ってる、みたいなことが起こる。
or even what its parent class should be.
S3 のクラスには親子関係がない。こういう感じでクラスを設定することはできるけど、このオブジェクトでは foo と bar がこの順序で並んでいると言うだけで、常にこうなっている保証はない。
class(x) <- c("foo", "bar")
When a new user encounter an S3 generic, they are often confused because the implementation of the function appears to be missing.
メソッドの実装がどこにあるのか探しづらい。そうかな...?と思うけどそうなのかも。まあ S7 の表示が見やすいのはほんと。
Properties of an S3 class are usually stored in attributes, but, by default,
attr()
does partial matching, ...略... Additionally,attr()
returnsNULL
if an attribute doesn’t exist
S3 の場合でよくあるパターンとして、プロパティは attribute に格納される。しかし、attribute を操作する attr()
には問題があって、partial matching してしまうし、attribute が存在しなくても NULL
を返してしまうので、タイポしていてもエラーにならずバグの温床になりがち。
S7 の @
は exact match しかしないようになってるので安全。
S3 method dispatch is complicated for compatibility with S. This complexity affects relatively little code, but when you attempt to dive into the details it makes
UseMethod()
hard to understand.
UseMethod()
がわかりづらい。なるほど、もう慣れてしまってなんとも思わないけど、引数も渡さずに関数を呼ぶの意味わからない、というのは言われてみればそうだなあ。
S3 is primarily designed for single dispatch and double dispatch is only provided for a handful of base generics.
double dispatch できない。これはほんとそうで、みんな困ってる点。たとえば ggplot2 の +
とか。
NextMethod()
is unpredictable since you can't tell exactly which method will be called
NextMethod()
で何が呼ばれるかわからない。たしかに...
Conversion between S3 classes is only implemented via loose convention: if you implement a class
foo
, then you should also provide genericas.foo()
to convert other objects to that type.
オブジェクトの変換は、なんとなく as.foo()
みたいなのを定義して使うことになってるけど、ちゃんとした規約はない。S7 は convert()
という double dispatch する関数が定義されている。
convert
#> <S7_generic> convert(from, to, ...) with 0 methods:
S4 のダメなところ
Multiple inheritance seemed like a powerful idea at the time, but in practice it appears to generate more problems than it solves.
S4 のクラスは、なんと複数の親クラスを持つことができる。何が利点なんだっけ...? とにかく複雑すぎて逆にいろいろ問題を引き起こしているので、S7 はそういうとこは採用しなかったよ、と。
S4's method dispatch uses a principled but complex distance metric to pick the best method in the presence of ambiguity. Time has shown that this approach is hard for people to understand and makes it hard to predict what will happen when new methods are registered.
メソッドディスパッチが複雑すぎて、多くの人が理解できない挙動になることがある。具体的にどういうケースを言ってるのかわからないけど、これも親クラスが複数あることに起因するなにかなのかもしれない。
S4 is a clean break from S3. This made it possible to make radical changes but it made it harder to switch from S3 to S4, leading to a general lack of adoption in the R community.
S4 は S3 と互換性がない。そのことでいろいろ革新的な変更も加えられるが、なんだかんだ R のシステムは S3 に支えられているので S3 に互換性がないと採用しづらいことになっている。
At least within Bioconductor, slots are generally thought of as implementation detail that should not be directly accessed by the end-user. This leads to two problems.
S4 のスロットは、内部実装であってユーザーは直接アクセスしないものだと考えられがちだが、そのためには必要なスロットすべてについていちいち getter/setter を用意しないといけないし、なにより実際にはアクセスできてしまう。
- バリデーションは、クラス定義時に
validate
にバリデーション用の関数を設定すると、それが自動で実行される。オブジェクトが変更されるたびにこれが走るので、あまり重いバリデーションには向いてないかもしれない。 - あるいは、プロパティごとに setter を指定できるので、そこでバリデーションをかけるのもありらしい。
- getter / setter は色々面白いことができるっぽい。たとえば deprecated のメッセージを出したりする例が紹介されている。
- S7 には S3 クラスが自動でつくので、S3 としても扱える
- S4 は難しすぎてちょっと理解できなかった
double dispatch はこんな感じで実現できるらしい。
speak <- new_generic("speak", c("x", "y"))
method(speak, list(dog, english)) <- function(x, y) "Woof"
method(speak, list(cat, english)) <- function(x, y) "Meow"
パッケージで使うときは .onLoad
でこれを実行する必要があるらしい。
.onLoad <- function(...) {
S7::methods_register()
}
ドキュメントの書き方にはまだちょっと課題があるっぽい。