Эмуляция множественной отправки в S3 (для 3 аргументов подписи)

Я осведомлен об общих входах и недостатках различных ОО системы в R, и я полностью за то, чтобы оставаться в S3, когда и сколько смогу.

Однако потребность в многократной отправке продолжает преследовать меня в профессиональных проектах и ​​продолжает тянуть меня в S4 в прошлое.

Но я отказываюсь признать, что я не могу найти S3 эмуляцию мультидиспетчеризации а-ля S4, которая, по крайней мере, способна поддерживать мой основной вариант использования — избегать бессистемных дрейф данных:

  1. Мои аналитические программы создают структуры данных, которые я публикую для заинтересованных сторон, чтобы они могли использовать их как часть некоторых конвейеров данных.
  2. Обычно я публикую в СУБД, такой как MongoDB, поэтому мои структуры в некоторой степени проявляют себя и могут/должны рассматриваться как схемы БД.
  3. Я придерживаюсь подхода «выпускайте раньше, выпускайте чаще», чтобы оставаться гибким, поэтому естественно, что «все меняется» — часто и со временем.
  4. Поскольку структуры данных в конечном итоге развиваются, я хочу подробно описать структуру, чтобы я мог определить процедуры внутренней миграции и / или информировать заинтересованные стороны о том, с какой версией структур данных они имеют дело в БД.

Если я собираюсь описать свои структуры данных с помощью какой-либо схемы управления версиями (это лучшее, что я мог придумать на данный момент), мне в основном нужно основывать свои методы на трех сигнатурных аргументах, касающихся отправки метода:

  1. Один для ввода (класса) данной задачи преобразования данных
  2. Один для версии ввода (класс)
  3. Один для версии вывода (класс)

Юридическая экспертиза

Помимо полевого руководства по OO я прочитал:

Мой подход

Последовательный подход на основе S3 для input_class и output_class, который может эволюционировать с течением времени в структурном отношении, который фиксируется соответствующими классами версий, такими как v1, v2 и т. д.

Шаг 1: Определите необходимые базовые компоненты

# Generic that dispatches on `x`
new_output_class <- function(x, schema_version_in, schema_version_out, ...) {
  UseMethod("new_output_class", x)
}

# Generic that dispatches on 'schema_version_in' and 
# can thus be used to define distinct methods for 
# different versions of 'input_class'
new_output_class__input_class <- function(x, schema_version_in, 
  schema_version_out, ...) {
  UseMethod("new_output_class__input_class", schema_version_in)
}

# Generic that dispatches on 'schema_version_out' and 
# can thus be used to define distinct methods for 
# different versions of 'output_class' for inputs of class `input_class`
# with version class `v1`
new_output_class__input_class__v1 <- function(x, schema_version_in, 
  schema_version_out, ...) {
  UseMethod("new_output_class__input_class__v1", schema_version_out)
}

# Top-level method for dispatch based on `input_class`
new_output_class.input_class <- function(
  x, 
  schema_version_in,
  schema_version_out
) {
  message("Level 1: method for `input_class`")

  new_output_class__input_class(x, schema_version_in, schema_version_out)
}

Шаг 2: Определите методы для начального случая input_class = v1 и output_class = v1

library(magrittr)
library(stringr)

# Method for `input_class` of version `v1`
new_output_class__input_class.v1 <- function(
  x, 
  schema_version_in,
  schema_version_out
) {
  # message("Sub-level 1: `input_class/in:v1`")
  message(stringr::str_glue("Level 2: method for `input_class/in:{schema_version_in}`"))

  new_output_class__input_class__v1(x, schema_version_in, schema_version_out)
}

# Method for `output_class` of version `v1` for inputs 
# of `input_class` of version `v1`
new_output_class__input_class__v1.v1 <- function(
  x, 
  schema_version_in,
  schema_version_out
) {
  message(stringr::str_glue("Level 3: method for `input_class/in:{schema_version_in}/out:{schema_version_out}`"))

  structure(x, 
    class = c(stringr::str_glue("output_class_{schema_version_out}"),
      "output_class")
  )
}

x <- structure(letters[1:3], class = "input_class")

schema_version_of_x <- structure("v1", class = "v1")
schema_version_of_new_output <- structure("v1", class = "v1")

new_output_class(x, schema_version_of_x, schema_version_of_new_output)
# Level 1: method for `input_class`
# Level 2: method for `input_class/in:v1`
# Level 3: method for `input_class/in:v1/out:v1`
# [1] "a" "b" "c"
# attr(,"class")
# [1] "output_class_v1" "output_class"   

Шаг 3. Обработка эволюции от output_class до v2

# Method for `output_class` of version `v2` for inputs 
# of `input_class` of version `v1`
new_output_class__input_class__v1.v2 <- function(
  x,
  schema_version_in,
  schema_version_out
) {
  message(stringr::str_glue("Level 3: method for `input_class/in:{schema_version_in}/out:{schema_version_out}`"))

  structure(x %>% rep(2),
    class = c(stringr::str_glue("output_class_{schema_version_out}"),
      "output_class")
  )
}

schema_version_of_new_output <- structure("v2", class = "v2")
new_output_class(x, schema_version_of_x, schema_version_of_new_output)
# Level 1: method for `input_class`
# Level 2: method for `input_class/in:v1`
# Level 3: method for `input_class/in:v1/out:v2`
# [1] "a" "b" "c" "a" "b" "c"
# attr(,"class")
# [1] "output_class_v2" "output_class"   

Шаг 4. Обработка эволюции от input_class до v2

# Generic for input `input_class` of version `v2`
new_output_class__input_class__v2 <- function(x, schema_version_in, 
  schema_version_out, ...) {
  UseMethod("new_output_class__input_class__v2", schema_version_out)
}

# Method for `input_class` of version `v2`
new_output_class__input_class.v2 <- function(
  x, 
  schema_version_in,
  schema_version_out
) {
  message(stringr::str_glue("Level 2: method for `input_class/in:{schema_version_in}`"))

  new_output_class__input_class__v2(x, schema_version_in, schema_version_out)
}

# Method for `output_class` of version `v1` for inputs 
# of `input_class` of version `v2`
new_output_class__input_class__v2.v1 <- function(
  x, 
  schema_version_in,
  schema_version_out
) {
  message(stringr::str_glue("Level 3: method for `input_class/in:{schema_version_in}/out:{schema_version_out}`"))

  structure(x %>% tolower(),
    class = c(stringr::str_glue("output_class_{schema_version_out}"),
      "output_class")
  )
}

x <- structure(LETTERS[1:3], class = "input_class")
schema_version_of_x <- structure("v2", class = "v2")
schema_version_of_new_output <- structure("v1", class = "v1")
new_output_class(x, schema_version_of_x, schema_version_of_new_output)
# Level 1: method for `input_class`
# Level 2: method for `input_class/in:v2`
# Level 3: method for `input_class/in:v2/out:v1`
# [1] "a" "b" "c"
# attr(,"class")
# [1] "output_class_v1" "output_class"   

# Method for `output_class` of version `v2` for inputs 
# of `input_class` of version `v2`
new_output_class__input_class__v2.v2 <- function(
  x, 
  schema_version_in,
  schema_version_out
) {
  message(stringr::str_glue("Level 3: method for `input_class/in:{schema_version_in}/out:{schema_version_out}`"))

  structure(x %>% tolower() %>% rep(2),
    class = c(stringr::str_glue("output_class_{schema_version_out}"),
      "output_class")
  )
}

schema_version_of_x <- structure("v2", class = "v2")
schema_version_of_new_output <- structure("v2", class = "v2")
new_output_class(x, schema_version_of_x, schema_version_of_new_output)
# Level 1: method for `input_class`
# Level 2: method for `input_class/in:v2`
# Level 3: method for `input_class/in:v2/out:v2`
# [1] "a" "b" "c" "a" "b" "c"
# attr(,"class")
# [1] "output_class_v2" "output_class"  

Плюсы

  1. Оно работает
  2. It's S3
  3. это систематическое

Минусы

  1. Это даже более многословно, чем любое решение на основе S4.
  2. мне это совсем не нравится

Кто-нибудь придумал лучшую эмуляцию мультидиспетчеризации в S3?


person Rappster    schedule 12.09.2019    source источник