Giter Site home page Giter Site logo

s7's People

Contributors

bbolker avatar bisaloo avatar colinfay avatar davisvaughan avatar dgkf avatar dmurdoch avatar espinielli avatar hadley avatar jameslairdsmith avatar jamierowen avatar jeroen avatar jimhester avatar jonthegeek avatar jtlandis avatar krlmlr avatar laresbernardo avatar lawremi avatar mmaechler avatar ramiromagno avatar t-kalinowski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

s7's Issues

`new_method()` has side effects

new_method() causes a side effect by registering the S3 method. I had expected that to happen in method<-(). Pragmatic users are already just substituting setMethod() for new_method(), which is not ideal.

Dispatching on "missing"

S4 lets us dispatch on "missing", i.e., an argument in the signature not being provided. This is a somewhat useful feature for multiple dispatch. Should/could we support that in R7?

How does constructor find initializer?

I think these are the four options that we discussed in the call:

function(x, y) Range@new(x = x, y = y)

# constructor function gets class object as first argument
# user facing constructor would need to drop this automatically
function(class, x, y) class@new(x = x, y = y)

# .class injected into enclosing environment
function(x, y) .class@new(x = x, y = y)

# newObject() looks for .class in enclosing scope
function(x, y) newObject(x = x, y = y)

Message passing OOP

MP OOP occasionally comes up, so I think it deserves a thread. Here are my initial thoughts.

Questions

  1. Will we eventually extend the new system to something that supports MP OOP?
  2. If so, what will happen to existing implementations, particularly R6?

Mutability

I strongly believe we should retain mutability by default in R6 and at least permit mutability in new systems. Mutability is important to MP OOP as a paradigm, and I think immutability would break R6 and downstream packages like shiny.

Efficiency

We have the opportunity to make MP OOP much more efficient in R. In R6, each new object creates deep copies of all the methods. We can fix this with global method registration.

library(pryr)
#> Registered S3 method overwritten by 'pryr':
#>   method      from
#>   print.bytes Rcpp
library(txtq)
object1 <- txtq(tempfile()) # R6 object
object2 <- txtq(tempfile())
method1 <- object1$push
method2 <- object2$push
address(method1) == address(method2) # Methods are deep copies in objects.
#> [1] FALSE
object_size(object1)
#> 145 kB
object_size(replicate(1e3, txtq(tempfile())))
#> 5.57 MB

Created on 2020-10-05 by the reprex package (v0.3.0)

I found the above behavior extremely limiting in targets. R6 is a perfect fit to express the infrastructure of a Make-like pipeline tool, but because of the memory inefficiency issue, I cannot use it everywhere. The current implementation has a hybrid of S3 and R6 to make the appropriate tradeoffs. The S3 objects in this case still need to be mutable, so they are environments. That's awkward because they really should be MP OO objects.

torch ran into a similar problem. Object instantiation was too slow because of all the method copying, so @dfalbel implemented a custom fit-for-purpose class system in mlverse/torch#38.

Question about reliance on S3

In the requirements document, the compatibility section states

Ideally the new system will be an extension of S3, because S3 is already at the bottom of the stack and many of the other systems have some compatibility with S3.

And the syntax section states

Ideally the entire API could be free of side effects and odd conventions, like making the . character significant in method names. Direct manipulation of class and generic objects should enable this.

In what way would we build on S3 without S3 syntax? Do we build a layer of sugar on top and ignore the dot-based dispatch underneath? Maybe hide the S3 mechanics inside the class contract?

Consider a common prefix?

I think it would be worth considering if a common prefix would be useful, as a way to very clearly signal what object system code is using, e.g.:

  • s7_class() instead of classObject()
  • s7_methods() to avoid clash with methods()
  • s7_properties() for consistency

(Just using s7 as placeholder since the goal is S3 + S4)

It's not possible (?) to overload a generic with a named argument after the ellipsis

Reproducible example:

## This works fine:
`metadata7<-` <- R7::new_generic("metadata7", signature = "x")

R7::method(`metadata7<-`, "Annotated7") <- function(x, ..., value) {
  if (!is.list(value)) {
    stop("replacement 'metadata' value must be a list")
  }
  if (!length(value)) {
    names(value) <- NULL # instead of character()
  }
  x@metadata <- value
  x
}
## This doesn't
`metadata7<-` <- R7::new_generic("metadata7", signature = c("x", "...", "value"))

R7::method(`metadata7<-`, "Annotated7") <- function(x, ..., value) {
  if (!is.list(value)) {
    stop("replacement 'metadata' value must be a list")
  }
  if (!length(value)) {
    names(value) <- NULL # instead of character()
  }
  x@metadata <- value
  x
}

`str(obj)` doesn't work

Reproducible example:

Annotated7 <- R7::new_class("Annotated7",
  properties = list(
    metadata = "list"
  )
)
str(a)

# Classes 'Annotated7', 'R7_object' Error in object[seq_len(ile)] : object of type 'S4' is not subsettable

Persistence of class membership

While I mostly agree that extending S3 is reasonable, I am a bit concerned about the fluidity of the class attribute for our purposes. Specifically,

  1. Ad hoc assignment is possible with class<-() etc.
  2. Attributes tend to get dropped from objects at surprising times.

Do we want safeguards and formalization around class membership? Maybe formal construction/casting/coercion in addition to the already proposed validation?

Additional requirement: reflection

I'd like to propose one additional major requirement:

  1. The system should be reflective

Given a class and a generic, you should be able to find the appropriate method without calling it. This is important for building tooling around the system.

Can we unify $ and @?

At least conditionally, by making it easier to provide a standard $ method. This would avoid users having to understand the difference.

`method<-` complications

The proposed method definition syntax unfortunately throws a error at eval time

`method<-` <- function(generic, class, value) {
  str(substitute(generic))
  str(generic)
  registerS3method(generic, class, value, envir = parent.frame())
}

method("mean", "numeric") <- function(x) 1
#> Error in method("mean", "numeric") <- function(x) 1: target of assignment expands to non-language object

This is because the first argument is not a language object. We could work around this by either defining a dummy language object

`method<-` <- function(dummy, generic, class, value) {
  registerS3method(generic, class, value, envir = parent.frame())
}

. <- NULL
method(., "mean", "numeric") <- function(x) 1
mean(5)
#> [1] 1

or by passing the generic function directly.

`method<-` <- function(generic, class, value) {
  str(substitute(generic))
  str(generic)
  registerS3method(generic, class, value, envir = parent.frame())
}

method(mean, "numeric") <- function(x) 1
#>  symbol *tmp*
#> function (x, ...)
#> Error in match(x, table, nomatch = 0L): 'match' requires vector arguments

The main problem with the latter is as you can see we no longer have access to the name, even through substitute() because of the intermediate *tmp* variable used in fun<- assignments.

A third alternative would be to use method["mean", "numeric"] <- function, which would work, though it looks somewhat odd.

method <- structure(list(), class = "method_definitions")

`[<-.method_definitions` <- function(x, generic, class, value) {
  registerS3method(generic, class, value, envir = parent.frame())
}

method["mean", "numeric"] <- function(x) 1

mean(5)
#> [1] 1

Interface evolution?

Should we consider the challenge of changing the interface of a generic (i.e. adding or removing arguments, or moving a generic between packages). These are all things that have come up for tidyverse packages, and I don't feel like we have a good solution for them.

Similarly, what about adding attributes to a class? What if, unbeknownst to you, a subclass is already using the attribute you add?

Syntax sugar for class creation

A "crazy" idea from @lawremi

MyClass <- Class(slot1 := integer,
                 slot2 := character = "default") :=
    SuperClass |> valid_when(cond1, cond2, ...)

(moving here to keep the design doc more of a record of decisions)

Traits

Following from Hadley's comment in #31,

Traits sound like a Java "interface", i.e., a method contract, except somewhat complicated by multiple dispatch. For example, for a two argument signature, the contract could specify:

MyTrait <- newTrait(signature("MyTrait", "ANY"), 
                    signature("SomeOtherClassOrTrait", "MyTrait"))

In practice, it's been sufficient to rely on duck typing, although it would be nice to have automatic checks. A nice thing about duck typing is that packages can define methods without even knowing about a trait, and interoperate by informal convention. Another potential feature would be automatically identifying whether a class conforms to a trait, without actually declaring it. Runtime checks could use that to fail early.

Properties vs object data

In S4, the slots have the data. In S3, the object is the data. It seems like we're planning to do both with this system. If we like this, when should something be a property and when should it be part of the object?

Propertyless objects are easier to understand, but property-only objects could give us record-style objects almost for free.

I guess it would be possible for the user to choose either extreme when the situation calls for it. Not sure if there's anything actionable other than establishing usage patterns and recommendations. I think it's worth pondering even at this early stage.

Literal types

A bit of a crazy idea, but is it worth considering defining a Class a literal value? Like a method with newClass(parent="foo") in its signature would only be dispatched to by the exact value "foo". This would be useful, for example, when conditioning on a file extension, or the value of "method" parameter. Essentially it would be using dispatch to implement switch/case statements. In the past, I've done something like new(paste0(fileExt, "File")) to implement this.

Prefix class name with package

For backward compatibility with S3, we need to generate a character class vector. If we use just the class name for this vector, we are implicitly using the name as an identifier for S3 dispatch. Is this something we need to worry about?

In other words, are we worried that pkgA defines newClass("A") and pkgB defines newClass("A"), and while dispatch in the new system can disambiguate, S3 dispatch does not?

Is serialisation in scope?

Obviously we can't change this without changes to base R, but is it still worth considering at the design phase? The primary use case is to allows object that use external pointers to be saved and reloaded.

R CMD check issues with using `@`

Concretely, if I comment out the call to globalVariables() in https://github.com/RConsortium/OOP-WG/blob/a96f7e02a1bc2f12a445d25b6e0b5efcb91bebf3/R/zzz.R#L41

I get the following result from R CMD check

N  checking R code for possible problems (2.8s)
   method_register: no visible binding for global variable ‘name’
     (/private/var/folders/9x/_8jnmxwj3rq1t90mlr6_0k1w0000gn/T/Rtmpl5QIrn/R
7.Rcheck/00_pkg_src/R7/R/method.R:15)
   object_new: no visible binding for global variable ‘parent’
     (/private/var/folders/9x/_8jnmxwj3rq1t90mlr6_0k1w0000gn/T/Rtmpl5QIrn/R
7.Rcheck/00_pkg_src/R7/R/object.R:13)
   object_new: no visible binding for global variable ‘constructor’
     (/private/var/folders/9x/_8jnmxwj3rq1t90mlr6_0k1w0000gn/T/Rtmpl5QIrn/R
7.Rcheck/00_pkg_src/R7/R/object.R:13)
   print.r7_object: no visible binding for global variable ‘name’
     (/private/var/folders/9x/_8jnmxwj3rq1t90mlr6_0k1w0000gn/T/Rtmpl5QIrn/R
7.Rcheck/00_pkg_src/R7/R/class.R:43)
   Undefined global functions or variables:
     constructor name parent

It seems this is partially the result of needing to redefine @ in R7. If @ was generic like @<- or $ we could just define a @.r7_object method and avoid this.

I am still tracking down where the filtering is happening in codetools, to see if there is another way we could avoid this.

How will method<- work in a package

It occurred to me that our proposed syntax for defining a method:

method(foo, class) <- function(x) { } 

Is going to be a little tricky when run from a package, since this code will be executed at package build time, and will need to somehow be replayed at package load time.

Where do the arguments for the method specification live?

Say we want to define a mean() method for class A. Assuming a hypothetical method<- function that we use to modify the global methods table, I think there are three ways we could supply the data:

method(mean, A) <- newMethod(function(x) 10)
# which could be shortened to 
method(mean, A) <- function(x) 10

method(mean) <- newMethod("A", function(x) 10)

methods() <- newMethod("mean", "A", function(x) 10)

(We could also do methods(A) <- newMethod("mean", function(x) 10) but I think that encourages the wrong mental model of the OO system)

I'm kind of partial to the first, because it means that we wouldn't need to have a user facing method constructor. But either way, I think we need to consider the design of the method/class/generic constructors in tandem with the assignment function that they use to have an impact on the state of the world.

[design] symbols for syntactic sugar

When it comes to syntactic sugar, I have a personal preference to overload operators that users are likely to already be familiar with. For example, although @ has precedent from S4, I rarely see it used elsewhere. Is there anything that prevents us from choosing the more ubiquitous $? Property values feel a lot like list elements to me.

Merging property values

@lawremi I extracted this paragraph from the design doc because it no longer seems to fit where it was, and I don't understand it enough to integrate elsewhere:

The second step, merging property values, is also useful for updating an object (immutably, so returning a new object). This corresponds to the methods::initialize(), which also runs the validator. We would separate the (re)initialization from validation. This would allow the developer to update the object in controlled situations without triggering potentially expensive or redundant validation. If we implement initialization through a generic, the developer can override it for a particular class.

Make method not found error more helpful

In the following examples, R7 does not throw relevant error messages compared to S4.
It would be helpful if it throws appropriate error messages.

R7

> library(R7)
> foo <- new_generic(name="foo", signature = "x")
> method(foo, "character") <- function(x, ...) paste0("bar:", x)
> foo("x")
[1] "bar:x"
> foo(1)
Error: No method found!
> foo()
Error: No method found!
> foo(x=1, y=2)
Error: No method found!

S4

> setGeneric("bar", function(x, ...) standardGeneric("bar"))
[1] "bar"
> setMethod("bar", "character", function(x, ...) paste0("foo:", x))
> bar("x")
[1] "foo:x"
> bar(1)
Error in (function (classes, fdef, mtable)  :
  unable to find an inherited method for functionbarfor signature"numeric"> bar()
Error in (function (classes, fdef, mtable)  :
  unable to find an inherited method for functionbarfor signature"missing"> bar(x=1, y=2)
Error in (function (classes, fdef, mtable)  :
  unable to find an inherited method for functionbarfor signature"numeric"

Class unions

The methods package defines class unions as new classes, inserted above the members. Messing with the class hierarchy causes all sorts of complications, and depends on multiple inheritance.

We could instead define class unions as instances, i.e., union(classA, classB) returns a ClassUnion object. Using a ClassUnion in a signature could define a separate method for each member of the union. Or, this could be encoded into the dispatch logic. Using a ClassUnion as a property type would tell the validator to return valid if the value is an instance of any of the members. I've seen this in Julia and TypeScript, and it's probably in many others. It probably helps that they're compiled.

TypeScript also has the notion of a type intersection, which makes sense given that it's entirely based on structural/duck typing. Not sure it applies in our case, particularly if we preclude multiple inheritance.

Class namespacing

Should we explicitly include the class namespacing problem? i.e. with S3, both pkgA and pkgB can define a class foo, and there's no distinction at the dispatch level. In adv-r I recommend using the package name as a class prefix to avoid this problem).

Be precise about compatibility

I think these are the 12 possible types of compatibility.

Inheritance:

  • R7 extends S3 object: yes
  • R7 extends S4 object: no?
  • S3 extends R7 object: yes, to support incremental replacement of base classes.
  • S4 extends R7 object: yes, via S3.

Composition:

  • R7 has S3 property: yes
  • R7 has S4 property: no
  • S3 has R7 attribute: yes, because no restrictions
  • S4 has R7 slot: yes, via S3

Methods:

  • R7 generic + S3 object: yes
  • R7 generic + S4 object: no?
  • S3 generic + R7 object: yes
  • S4 generic + R7 object: yes, via S3

I think we can get away with not supporting S4 in R7 as long as we assume that any S4 -> R7 conversion proceeds from the inside out (i.e. start with the base class and it propagates out over time)

Dealing with dots

Here is an example which I think nicely illustrates the difficulties regarding dots in methods.
We define a print_r7 generic and then want to write a print_list function that calls print_r7 on each of the elements in the list.

library(R7)

print_r7 <- new_generic("print_r7", "x")

method(print_r7, "character") <- function(x) cat("<chr> ", x, "\n", sep = "")
method(print_r7, "numeric") <- function(x, digits = 4) cat("<num> ", formatC(x, format = "fg", digits = digits), "\n", sep = "")

print_list <- function(x, ...) {
  for (elem in x) {
    print_r7(elem, ...)
  }
}

This works fine if you don’t specify any additional optional arguments

print_list(list("foo", pi))
#> <chr> foo
#> <num> 3.142

But as soon as you specify an optional argument that only exists in one of the methods you get an error.

print_list(list("foo", pi), digits = 10)
#> Error in print_r7(elem, ...): unused argument (digits = 10)

Requiring the methods to all take dots resolves this 

method(print_r7, "character") <- function(x, ...) cat("<chr> ", x, "\n", sep = "")
method(print_r7, "numeric") <- function(x, ..., digits = 4) cat("<num> ", formatC(x, format = "fg", digits = digits), "\n", sep = "")

print_list(list("foo", pi), digits = 10)
#> <chr> foo
#> <num> 3.141592654

But then typos for optional arguments are silently ignored.

print_list(list("foo", pi), digist = 5)
#> <chr> foo
#> <num> 3.142

One option would be to lookup the method, query it for non-dispatched arguments, then only pass those on when we call it.

print_list <- function(x, ...) {
  args <- list(...)
  for (elem in x) {
    f <- method(print_r7, list(object_class(elem)))
    extra_arg_names <- intersect(names(formals(f)), names(args))
    extra_args <- args[extra_arg_names]
    do.call(f, c(elem, extra_args))
  }
}
print_list(list("foo", pi), digits = 10)
#> <chr> foo
#> <num> 3.141592654

This resolves the error, but unfortunately doesn’t actually help us detect typos

print_list(list("foo", pi), digist = 5)
#> <chr> foo
#> <num> 3.142

We would have to do the above and also track what arguments we have passed, throwing an error if there are still unused arguments.

print_list <- function(x, ...) {
  args <- list(...)
  used_args <- character()
  for (elem in x) {
    f <- method(print_r7, list(object_class(elem)))
    extra_arg_names <- intersect(names(formals(f)), names(args))
    used_args <- unique(c(used_args, extra_arg_names))
    extra_args <- args[extra_arg_names]
    do.call(f, c(elem, extra_args))
  }
  unused_args <- !names(args) %in% used_args
  if (any(unused_args)) {
    stop(sprintf("%s unused", paste0(names(args)[unused_args], collapse = ", ")), call. = FALSE)
  }
}
print_list(list("foo", pi))
#> <chr> foo
#> <num> 3.142
print_list(list("foo", pi), digits = 10)
#> <chr> foo
#> <num> 3.141592654
print_list(list("foo", pi), digist = 5)
#> <chr> foo
#> <num> 3.142
#> Error: digist unused

I am not sure how we could do this generally, as this only works if all the methods use dots for optional arguments.
At least some methods use dots to pass data or some other NSE cases.
It is also not clear to me how R7 would collect the used arguments and know that all of the potential calls were complete.

If anyone has ideas on other ways to address this problem I would be happy to try them.

Method validation?

Should we consider method validation to be a requirement of the system? i.e on method creation should we validate that the arguments are compatible with the generic? (this is currently performed by R CMD check, but making the error closer to definition might be useful)

Similarly, should we enforce who is allowed to define methods? As generally, you should only define a method if you "own" either the generic or the class. We could enforce this by only permitting method definition if the class or generic exists in the current package (this is a fairly narrow definition of ownership).

Method reflection

Given that method(generic, class) <- method defines a method (assuming that #35 is merged), I propose that method(g, s) retrieve the method for the generic g for signature s. I think this should be relatively uncontroversial, but there are a couple of questions:

  • What should happen if there is no method — should it return an error or NULL? I think NULL will be easier to program with since it is not common to use tryCatch() for control flow in R.

  • Should you be able to request a specific method? (i.e. ignore inheritance)

[design] choice of structure for property and method lists

Should the property list and the method list actually be lists objects? (Or did I read that part of the design too literally?) We could childproof them if we create them as environments and lock them. Since they belong to class structures and not instantiated objects, I think the performance hit would be negligible.

defineClass arguments

Any objections to changing from:

defineClass(
  name, 
  parent = object, 
  constructor = function(...) newObject(...), 
  validity = function(object) NULL,
  properties = list()
)

to

defineClass(
  name, 
  parent = Object, 
  constructor = function(...) newObject(...), 
  validator = function(x) NULL,
  properties = list()
)

i.e. renaming validity to validator (to match constructor) and changing the argument to x (since it's shorter but I think still conveys genericity).

Delayed registration?

Should we explicitly consider delayed registration in the requirements? This is needed if you want to provide a method for a generic (or class) that is available in a suggested (not imported) package.

R-exsts has:

As from R 3.6.0 one can also use S3method() directives to perform delayed registration. With

if(getRversion() >= "3.6.0") {
   S3method(pkg::gen, cls)
}

Clarify constructor

@lawremi could you clarify how you expect the constructor to work a little more? I think you're implying something like this?

Range <- defineClass("Range", 
  properties = list(start = Numeric, end = Numeric)
)
new_range <- function(start, end) {
    stopifnot(is.numeric(start), is.numeric(end), end >= start)
    Range@new(start=start, end=end)
}

(This also raises the question of whether or not we'd explicitly expose the most important base S3 classes in this new system)

But then I can't see how that ties in with the class object being a function, because I'd then expect you to call Range(1, 2) to construct an object. So maybe you meant something more like this?

Range@init <- function(start, end) {
    stopifnot(is.numeric(start), is.numeric(end), end >= start)
    Range@new(start=start, end=end)
}

But then you'd have to define the constructor after you've defined the class; otherwise I think it would be weird to use Range@new() (because Range doesn't exist at the time you're writing the function; which obviously isn't impossible, it just feels weird to me).

[design] when to validate objects

Thanks for drafting the design document, @lawremi.

Early on, you wrote:

Initializing an instance of a class will:

  1. Create the prototype, by either adding the class label to the parent instance or constructing an empty S4SXP,
  2. Merge any given property values, and
  3. Validate and return the result.

I like that we have a dedicated place for validation, but perhaps we should touch base about when we want it to happen. Just concerned about the potential overhead of validating on every initialization.

Remove method list from class object

I'd suggest removing the method list from the class object, at least for an initial round of discussion. I think it makes it seems like you're proposing a bigger change (from generic function OO to encapsulated OO) than I think you are, and it seems like it might be distracting at this point in the discussions. I also don't love the idea of having a mutable object here as it would make cleanly detaching packages even more complex than it is currently.

Should properties have defaults?

The downside is that the constructors have to provide default values for their arguments, so the default values will typically be encoded in two places, which makes inconsistency possible.

Error when spawning an object

Reproducible example:

Annotated7 <- R7::new_class("Annotated7",
  properties = list(
    metadata = "list"
  )
)
setClassUnion("DataFrame_OR_NULL7", "NULL")

Vector7 <- R7::new_class("Vector7",
  parent = Annotated7,
  properties = list(
    elementMetadata = "DataFrame_OR_NULL7"
  )
)
v <- Vector7()

# Error in do.call(base::`@`, list(object, name)) :
#   trying to get slot "parent" from an object of a basic class ("character") with no slots

A brief investigation revealed that when the parent constructor (constructor of Annotated7)
is run from the constructor of Vector7, it incorrectly interprets object_class(sys.function(-1))
as "function" (a character string) instead of an R7 class, and tries to access a slot of that string.
(The problem doesn't occur when the constructor of Annotated7 is executed directly.)
sys.function(-1) seems to be the culprit.

Uniqueness of class objects

If I have two class objects of the same name:

A1 <- newClass("A")
A2 <- newClass("A")

And defined a method for A1:

method(mean, A1) <- function(x) 10

How would dispatch react to passing an instance of A2?

mean(A2())

Does each call to newClass() do something like allocate a UUID that persists for the lifetime of the object? At the very least we would want to capture the calling environment (package namespace) to distinguish classes of the same name in different packages.

The syntax is sometimes inconsistent

For example, class/function names sometimes have to be quoted, sometimes can't be quoted,
and sometimes can, but don't have to be quoted – in the latter case, I'm not sure if using it
incorrectly will not cause an error further down the road.

Method call frame

The dispatch document proposes a possible alternative to UseMethod() that acts like an ordinary function call. What would it take to implement this without call frame issues? cc @lionel-.

I understand that maybe we want parent frames to agree if we want deparse(substitute(arg)) to work (or if the user just wants to get the parent frame from inside a method). Seems like we could somehow make the actual call frames agree as well. Current S3 behavior:

foo <- function(x, y)  {
  print(environment())
  print(parent.frame())
  UseMethod("foo")
}
foo.numeric <- function(x, y) {
  print(environment())
  print(parent.frame())
}
foo(1, 1)
#> <environment: 0x7fa55a559e60>
#> <environment: R_GlobalEnv>
#> <environment: 0x7fa55a551518>
#> <environment: R_GlobalEnv>

Goal for R7?

foo <- function(x, y)  {
  print(environment())
  print(parent.frame())
  use_r7_method("foo")
}
foo.numeric <- function(x, y) {
  print(environment())
  print(parent.frame())
}
foo(1, 1)
#> <environment: 0x7fa55a559e60>
#> <environment: R_GlobalEnv>
#> <environment: 0x7fa55a559e60>
#> <environment: R_GlobalEnv>

I think these same considerations apply to NextMethod() and its possible alternatives.

[design] include the validator in the class structure?

From the top of the design doc:

In accordance with requirement #2, classes are first class objects, with the following components:

  • Name
  • Parent class object
    • Single inheritance (requirement #6)
  • Property list
  • Method list
    • We are able to assign methods to a class, because we assume single and nested (requirement #5) dispatch.
  • Initializer

Should we include the validator too?

Related: #17

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.