算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
or `attr()` (unsafe) | `@` (safe, validated) | Property validation matters |\n| **Validation** | Manual, inconsistent | Built-in validators | Data integrity important |\n| **Method discovery** | Hard to find methods | Clear method printing | Developer experience matters |\n| **Multiple dispatch** | Limited (base generics) | Full multiple dispatch | Complex method dispatch needed |\n| **Inheritance** | Informal, `NextMethod()` | Explicit `super()` | Predictable inheritance needed |\n| **Migration cost** | - | Low (1-2 hours) | Want better structure |\n| **Performance** | Fastest | ~Same as S3 | Performance difference negligible |\n| **Compatibility** | Full S3 | Full S3 + S7 | Need both old and new patterns |\n\n## Practical Guidelines\n\n### Choose S7 when you have\n\n```r\n# Complex validation needs\nRange \u003c- new_class(\"Range\",\n properties = list(start = class_double, end = class_double),\n validator = function(self) {\n if (self@end \u003c self@start) \"@end must be >= @start\"\n }\n)\n\n# Multiple dispatch needs\nmethod(generic, list(ClassA, ClassB)) \u003c- function(x, y) ...\n\n# Class hierarchies with clear inheritance\nChild \u003c- new_class(\"Child\", parent = Parent)\n```\n\n### Choose vctrs when you need\n\n```r\n# Vector-like behavior in data frames\npercent \u003c- new_vctr(0.5, class = \"percentage\")\ndata.frame(x = 1:3, pct = percent(c(0.1, 0.2, 0.3))) # works seamlessly\n\n# Type-stable operations\nvec_c(percent(0.1), percent(0.2)) # predictable behavior\nvec_cast(0.5, percent()) # explicit, safe casting\n```\n\n### Choose S3 when you have\n\n```r\n# Simple classes without complex needs\nnew_simple \u003c- function(x) structure(x, class = \"simple\")\nprint.simple \u003c- function(x, ...) cat(\"Simple:\", x)\n\n# Maximum performance needs (rare)\n# Existing S3 ecosystem contributions\n```\n\n## S3 Patterns\n\n### Basic S3 Class\n\n```r\n# Constructor\nnew_person \u003c- function(name, age) {\n stopifnot(is.character(name), length(name) == 1)\n stopifnot(is.numeric(age), length(age) == 1)\n\n structure(\n list(name = name, age = age),\n class = \"person\"\n )\n}\n\n# Print method\nprint.person \u003c- function(x, ...) {\n cat(\"Person:\", x$name, \"(age\", x$age, \")\\n\")\n invisible(x)\n}\n\n# Generic + method\ngreet \u003c- function(x) UseMethod(\"greet\")\ngreet.person \u003c- function(x) {\n cat(\"Hello, my name is\", x$name, \"\\n\")\n}\ngreet.default \u003c- function(x) {\n cat(\"Hello!\\n\")\n}\n```\n\n### S3 Inheritance\n\n```r\n# Child class\nnew_employee \u003c- function(name, age, company) {\n obj \u003c- new_person(name, age)\n obj$company \u003c- company\n class(obj) \u003c- c(\"employee\", class(obj))\n obj\n}\n\n# Method with inheritance\nprint.employee \u003c- function(x, ...) {\n NextMethod() # Call parent print method\n cat(\"Works at:\", x$company, \"\\n\")\n invisible(x)\n}\n```\n\n## S7 Patterns\n\n### Basic S7 Class\n\n```r\nlibrary(S7)\n\n# Define class\nPerson \u003c- new_class(\"Person\",\n properties = list(\n name = class_character,\n age = class_numeric\n ),\n validator = function(self) {\n if (self@age \u003c 0) {\n \"@age must be non-negative\"\n }\n }\n)\n\n# Create instance\nbob \u003c- Person(name = \"Bob\", age = 30)\nbob@name # \"Bob\"\nbob@age \u003c- 31 # Validated assignment\n```\n\n### S7 Methods\n\n```r\n# Define generic\ngreet \u003c- new_generic(\"greet\", \"x\")\n\n# Add method\nmethod(greet, Person) \u003c- function(x) {\n cat(\"Hello, my name is\", x@name, \"\\n\")\n}\n\n# Default method\nmethod(greet, class_any) \u003c- function(x) {\n cat(\"Hello!\\n\")\n}\n```\n\n### S7 Inheritance\n\n```r\nEmployee \u003c- new_class(\"Employee\",\n parent = Person,\n properties = list(\n company = class_character\n )\n)\n\n# Override method\nmethod(greet, Employee) \u003c- function(x) {\n super(x, Person)@greet() # Call parent method\n cat(\"I work at\", x@company, \"\\n\")\n}\n```\n\n### S7 Multiple Dispatch\n\n```r\n# Generic with multiple dispatch\ncombine \u003c- new_generic(\"combine\", c(\"x\", \"y\"))\n\n# Method for specific combination\nmethod(combine, list(Person, Person)) \u003c- function(x, y) {\n cat(x@name, \"meets\", y@name, \"\\n\")\n}\n\nmethod(combine, list(Person, class_character)) \u003c- function(x, y) {\n cat(x@name, \"receives message:\", y, \"\\n\")\n}\n```\n\n## Migration Strategy\n\n1. **S3 -> S7**: Usually 1-2 hours work, keeps full compatibility\n2. **S4 -> S7**: More complex, evaluate if S4 features are actually needed\n3. **Base R -> vctrs**: For vector-like classes, significant benefits\n4. **Combining approaches**: S7 classes can use vctrs principles internally\n\n### Migration Example: S3 to S7\n\n```r\n# Original S3\nnew_person_s3 \u003c- function(name, age) {\n structure(list(name = name, age = age), class = \"person\")\n}\n\n# Migrated S7\nPerson \u003c- new_class(\"Person\",\n properties = list(\n name = class_character,\n age = class_numeric\n )\n)\n\n# S7 is backwards compatible with S3 generics\n# Existing S3 methods still work\n```\n\n## When NOT to Use OOP\n\nSometimes simpler approaches are better:\n\n```r\n# Don't create a class for simple data\n# BAD\nPoint \u003c- new_class(\"Point\", properties = list(x = class_double, y = class_double))\n\n# GOOD - just use a named list or vector\npoint \u003c- c(x = 1.5, y = 2.3)\n\n# Don't create classes for one-off operations\n# Use functions instead\ndistance \u003c- function(p1, p2) {\n sqrt((p1[\"x\"] - p2[\"x\"])^2 + (p1[\"y\"] - p2[\"y\"])^2)\n}\n```\n","licenseDocuments":[]}}
r-oop【设计与创意】R object-oriented programming guide for S7, S3, S4, and vctrs. Use when designing R classes or choosing an OOP system.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
S7, S3, S4, and vctrs: choosing the right OOP system for your needs
# S7 class definition
Range <- new_class("Range",
properties = list(
start = class_double,
end = class_double
),
validator = function(self) {
if (self@end < self@start) {
"@end must be >= @start"
}
}
)
# Usage - constructor and property access
x <- Range(start = 1, end = 10)
x@start # 1
x@end <- 20 # automatic validation
# Methods
inside <- new_generic("inside", "x")
method(inside, Range) <- function(x, y) {
y >= x@start & y <= x@end
}
Start here: What are you building?
Use vctrs when:
- Need data frame integration (columns/rows)
- Want type-stable vector operations
- Building factor-like, date-like, or numeric-like classes
- Need consistent coercion/casting behavior
- Working with existing tidyverse infrastructure
Examples: custom date classes, units, categorical data
Use S7 when:
- NEW projects that need formal classes
- Want property validation and safe property access (@)
- Need multiple dispatch (beyond S3's double dispatch)
- Converting from S3 and want better structure
- Building class hierarchies with inheritance
- Want better error messages and discoverability
Use S3 when:
- Simple classes with minimal structure needs
- Maximum compatibility and minimal dependencies
- Quick prototyping or internal classes
- Contributing to existing S3-based ecosystems
- Performance is absolutely critical (minimal overhead)
Use S4 when:
- Working in Bioconductor ecosystem
- Need complex multiple inheritance (S7 doesn't support this)
- Existing S4 codebase that works well
| Feature | S3 | S7 | When S7 wins |
|---|---|---|---|
| Class definition | Informal (convention) | Formal (new_class()) | Need guaranteed structure |
| Property access | $ or attr() (unsafe) | @ (safe, validated) | Property validation matters |
| Validation | Manual, inconsistent | Built-in validators | Data integrity important |
| Method discovery | Hard to find methods | Clear method printing | Developer experience matters |
| Multiple dispatch | Limited (base generics) | Full multiple dispatch | Complex method dispatch needed |
| Inheritance | Informal, NextMethod() | Explicit super() | Predictable inheritance needed |
| Migration cost | - | Low (1-2 hours) | Want better structure |
| Performance | Fastest | ~Same as S3 | Performance difference negligible |
| Compatibility | Full S3 | Full S3 + S7 | Need both old and new patterns |
# Complex validation needs
Range <- new_class("Range",
properties = list(start = class_double, end = class_double),
validator = function(self) {
if (self@end < self@start) "@end must be >= @start"
}
)
# Multiple dispatch needs
method(generic, list(ClassA, ClassB)) <- function(x, y) ...
# Class hierarchies with clear inheritance
Child <- new_class("Child", parent = Parent)
# Vector-like behavior in data frames
percent <- new_vctr(0.5, class = "percentage")
data.frame(x = 1:3, pct = percent(c(0.1, 0.2, 0.3))) # works seamlessly
# Type-stable operations
vec_c(percent(0.1), percent(0.2)) # predictable behavior
vec_cast(0.5, percent()) # explicit, safe casting
# Simple classes without complex needs
new_simple <- function(x) structure(x, class = "simple")
print.simple <- function(x, ...) cat("Simple:", x)
# Maximum performance needs (rare)
# Existing S3 ecosystem contributions
# Constructor
new_person <- function(name, age) {
stopifnot(is.character(name), length(name) == 1)
stopifnot(is.numeric(age), length(age) == 1)
structure(
list(name = name, age = age),
class = "person"
)
}
# Print method
print.person <- function(x, ...) {
cat("Person:", x$name, "(age", x$age, ")\n")
invisible(x)
}
# Generic + method
greet <- function(x) UseMethod("greet")
greet.person <- function(x) {
cat("Hello, my name is", x$name, "\n")
}
greet.default <- function(x) {
cat("Hello!\n")
}
# Child class
new_employee <- function(name, age, company) {
obj <- new_person(name, age)
obj$company <- company
class(obj) <- c("employee", class(obj))
obj
}
# Method with inheritance
print.employee <- function(x, ...) {
NextMethod() # Call parent print method
cat("Works at:", x$company, "\n")
invisible(x)
}
library(S7)
# Define class
Person <- new_class("Person",
properties = list(
name = class_character,
age = class_numeric
),
validator = function(self) {
if (self@age < 0) {
"@age must be non-negative"
}
}
)
# Create instance
bob <- Person(name = "Bob", age = 30)
bob@name # "Bob"
bob@age <- 31 # Validated assignment
# Define generic
greet <- new_generic("greet", "x")
# Add method
method(greet, Person) <- function(x) {
cat("Hello, my name is", x@name, "\n")
}
# Default method
method(greet, class_any) <- function(x) {
cat("Hello!\n")
}
Employee <- new_class("Employee",
parent = Person,
properties = list(
company = class_character
)
)
# Override method
method(greet, Employee) <- function(x) {
super(x, Person)@greet() # Call parent method
cat("I work at", x@company, "\n")
}
# Generic with multiple dispatch
combine <- new_generic("combine", c("x", "y"))
# Method for specific combination
method(combine, list(Person, Person)) <- function(x, y) {
cat(x@name, "meets", y@name, "\n")
}
method(combine, list(Person, class_character)) <- function(x, y) {
cat(x@name, "receives message:", y, "\n")
}
# Original S3
new_person_s3 <- function(name, age) {
structure(list(name = name, age = age), class = "person")
}
# Migrated S7
Person <- new_class("Person",
properties = list(
name = class_character,
age = class_numeric
)
)
# S7 is backwards compatible with S3 generics
# Existing S3 methods still work
Sometimes simpler approaches are better:
# Don't create a class for simple data
# BAD
Point <- new_class("Point", properties = list(x = class_double, y = class_double))
# GOOD - just use a named list or vector
point <- c(x = 1.5, y = 2.3)
# Don't create classes for one-off operations
# Use functions instead
distance <- function(p1, p2) {
sqrt((p1["x"] - p2["x"])^2 + (p1["y"] - p2["y"])^2)
}