読者です 読者をやめる 読者になる 読者になる

メモ:switch()の代わりにS3 generic functionを使う

R S3

たとえばこんなデータがあるとする。

{
  "日付": {
    "type": "date",
    "value": "2015-11-11"
  },
  "何の日": {
    "type": "char",
    "value": "ポッキーの日"
  },
  "俺の中での重要度": {
    "type": "int",
    "value": "3"
  }
}

これを読むときに、愚直にやるとたぶんこんな感じ。

library(jsonlite)
library(purrr)

j <- fromJSON("test.json")

map(j,
    ~ switch(.$type,
             date = as.Date(.$value),
             char = as.character(.$value),
             int  = as.integer(.$value)))
#> $`日付`
#> [1] "2015-11-11"
#> 
#> $何の日
#> [1] "ポッキーの日"
#> 
#> $俺の中での重要度
#> [1] 3

これを、S3 generic functionを使う手もあることにふと気づいた。

まず、typeの値をclassとして設定する。

j_w_class <- map(j, ~ structure(.$value, class = .$type))
j_w_class
#> $`日付`
#> [1] "2015-11-11"
#> attr(,"class")
#> [1] "date"
#> 
#> $何の日
#> [1] "ポッキーの日"
#> attr(,"class")
#> [1] "char"
#> 
#> $俺の中での重要度
#> [1] "3"
#> attr(,"class")
#> [1] "int"

classごとのgeneric functionを用意する。

parse_j <- function(x) UseMethod("parse_j")

parse_j.default <- function(x) as.character(x)
parse_j.date <- function(x) as.Date(x)
parse_j.char <- function(x) as.character(x)
parse_j.int  <- function(x) as.integer(x)

これを適用する。

map(j_w_class, parse_j)
#> $`日付`
#> [1] "2015-11-11 UTC"
#> 
#> $何の日
#> [1] "ポッキーの日"
#> 
#> $俺の中での重要度
#> [1] 3

これはたぶん、Domain specific languages · Advanced R.の例のHTMLみたいに、ネストする構造がある場合に見通しがよくなる気がする。switch()でも再帰すればいいだけではあるけど。

はまりどころ

これをパッケージ化するときにちょっとはまったのでメモ。(このへんはS3 generic functionというよりroxygen2の仕様の問題なのかも)

#' @export
f <- function(x) UseMethod("f")

f.default <- function(x) as.character(x)

とかやると、あたりまえだけどf.default()はエクスポートされない。ggplot2:::print.ggplot()とかを見て、S3のメソッドって見えないからエクスポートしないものだと思い込んでたけど違った。

あと、

#' @export
f.default <- as.character

とかいう感じで別のS3 generic functionを代入するのもできない。上の例だと、S3Method(f.default)じゃなくてexport(f.default)されてしまう。