R6

R6 has two special properties:

  • It uses the encapsulated OOP paradigm, which means that methods belong to objects, not generics, and you call them like object$method().
  • R6 objects are mutable, which means that they are modified in place, and hence have reference semantics.(具有引用语义)

    Prerequisites

    环境总是在原地修改。此属性有时被描述为引用语义,因为当您修改环境时,与该环境的所有现有绑定继续具有相同的引用。
    1. e1 <- rlang::env(a = 1, b = 2, c = 3)
    2. e2 <- e1
    3. e1$c <- 4
    4. e2$c
    5. #> [1] 4

    Classes and methods

    R6创建的唯一方法:R6Class():
  1. 第一个参数是classname,表示创建的class的名字,通常首字母大写
  2. 第二个参数public提供构成对象公共接口的方法(函数)和字段(其他)的列表。通常小写
    1. Accumulator <- R6Class("Accumulator", list(
    2. sum = 0,
    3. add = function(x = 1) {
    4. self$sum <- self$sum + x
    5. invisible(self)
    6. })
    7. )
    通过调用new()方法从类中构造一个新对象。在 R6 中,方法属于对象,因此您可以使用$来访问new():
    1. x <- Accumulator$new()
    访问字段
    1. x$add(4)
    2. x$sum
    3. #> [1] 4

    Method chaining

    R6 方法应始终self不可见地返回。这将返回“当前”对象,并可以将多个方法调用链接在一起
    1. x$add(10)$add(10)$sum
    2. #> [1] 24
    如果没有invisible(self), 那将无法多个方法链接调用

    Important methods

    应该为大多数类定义两个重要的方法:$initialize()和$print(). 它们不是必需的,但提供它们将使您的课程更易于使用。
    $initialize() overrides the default behaviour of $new().

    以下代码确定输入的name是字符串,age是数值,并且只有唯一输入值

  1. Person <- R6Class("Person", list(
  2. name = NULL,
  3. age = NA,
  4. initialize = function(name, age = NA) {
  5. stopifnot(is.character(name), length(name) == 1)
  6. stopifnot(is.numeric(age), length(age) == 1)
  7. self$name <- name
  8. self$age <- age
  9. }
  10. ))
  11. hadley <- Person$new("Hadley", age = "thirty-eight")
  12. #> Error in initialize(...): is.numeric(age) is not TRUE
  13. hadley <- Person$new("Hadley", age = 38)

同理。定义$print()允许您覆盖默认打印行为。与任何因其副作用而调用的 R6 方法一样,$print()应该返回invisible(self).

  1. Person <- R6Class("Person", list(
  2. name = NULL,
  3. age = NA,
  4. initialize = function(name, age = NA) {
  5. self$name <- name
  6. self$age <- age
  7. },
  8. print = function(...) {
  9. cat("Person: \n")
  10. cat(" Name: ", self$name, "\n", sep = "")
  11. cat(" Age: ", self$age, "\n", sep = "")
  12. invisible(self)
  13. }
  14. ))
  15. hadley2 <- Person$new("Hadley")
  16. hadley2
  17. #> Person:
  18. #> Name: Hadley
  19. #> Age: NA
  20. hadley
  21. #> <Person>
  22. #> Public:
  23. #> age: 38
  24. #> clone: function (deep = FALSE)
  25. #> initialize: function (name, age = NA)
  26. #> name: Hadley
  27. hadley$print
  28. #> NULL

上述代码说明了 R6 的一个重要方面。因为方法绑定到单个对象,先前创建的hadley对象没有得到这个新方法:

Adding methods after creation

在已有的class的基础上再添加新的方法,. Add new elements to an existing class with $set()

  1. Accumulator <- R6Class("Accumulator")
  2. Accumulator$set("public", "sum", 0)
  3. Accumulator$set("public", "add", function(x = 1) {
  4. self$sum <- self$sum + x
  5. invisible(self)
  6. })

Inheritance

创建class元素的时候,提供了inherit argument

  1. AccumulatorChatty <- R6Class("AccumulatorChatty",
  2. inherit = Accumulator,
  3. public = list(
  4. add = function(x = 1) {
  5. cat("Adding ", x, "\n", sep = "")
  6. super$add(x = x)
  7. }
  8. )
  9. )
  10. x2 <- AccumulatorChatty$new()
  11. x2$add(10)$add(1)$sum
  12. #> Adding 10
  13. #> Adding 1
  14. #> [1] 11

add()会覆盖之前父类的add方法,但是 通过使用$super,就可以使用父类的方法

Introspection

  1. class(hadley2)
  2. #> [1] "Person" "R6"

每个 R6 对象都有一个反映其 R6 类层次结构的 S3 类。这意味着确定类(及其继承的所有类)的最简单方法是使用class()

Controlling access

R6Class()还有两个与 类似的参数public:

  • private允许您创建仅在类内部而不是外部可用的字段和方法。
  • active允许您使用访问器函数来定义动态或活动字段。

这些将在以下部分中进行描述。

private

  1. Person <- R6Class("Person",
  2. public = list(
  3. initialize = function(name, age = NA) {
  4. private$name <- name
  5. private$age <- age
  6. },
  7. print = function(...) {
  8. cat("Person: \n")
  9. cat(" Name: ", private$name, "\n", sep = "")
  10. cat(" Age: ", private$age, "\n", sep = "")
  11. }
  12. ),
  13. private = list(
  14. age = NA,
  15. name = NULL
  16. )
  17. )
  18. hadley3 <- Person$new("Hadley")
  19. hadley3
  20. #> Person:
  21. #> Name: Hadley
  22. #> Age: NA
  23. hadley3$name
  24. #> NULL

active field

  1. Rando <- R6::R6Class("Rando", active = list(
  2. random = function(value) {
  3. if (missing(value)) {
  4. runif(1)
  5. } else {
  6. stop("Can't set `$random`", call. = FALSE)
  7. }
  8. }
  9. ))
  10. x <- Rando$new()
  11. x$random
  12. #> [1] 0.0808
  13. x$random
  14. #> [1] 0.834
  15. x$random
  16. #> [1] 0.601

活动字段和私有字段的结合

  1. Person <- R6Class("Person",
  2. private = list(
  3. .age = NA,
  4. .name = NULL
  5. ),
  6. active = list(
  7. age = function(value) {
  8. if (missing(value)) {
  9. private$.age
  10. } else {
  11. stop("`$age` is read only", call. = FALSE)
  12. }
  13. },
  14. name = function(value) {
  15. if (missing(value)) {
  16. private$.name
  17. } else {
  18. stopifnot(is.character(value), length(value) == 1)
  19. private$.name <- value
  20. self
  21. }
  22. }
  23. ),
  24. public = list(
  25. initialize = function(name, age = NA) {
  26. private$.name <- name
  27. private$.age <- age
  28. }
  29. )
  30. )
  31. hadley4 <- Person$new("Hadley", age = 38)
  32. hadley4$name
  33. #> [1] "Hadley"
  34. hadley4$name <- 10
  35. #> Error in (function (value) : is.character(value) is not TRUE
  36. hadley4$age <- 20
  37. #> Error: `$age` is read only

Reference semantics

Why R6?

View source