how can I pass argument (names) to a function operator? (point-free programming)

83 views Asked by At

Background

Say, I have a ClassA and ClassB, each of which require their own arguments in the respective constructor functions:

ClassA <- function(A_arg1, A_arg2) {
  # some class-SPECIFIC construction magic happens, say
  out <- list(A_arg1, A_arg2)

  # some GENERAL construction magic happens
  class(out) <- "ClassA"

  return(out)
}

ClassB <- function(B_arg1, B_arg2) {
  # some class-SPECIFIC construction magic happens, say
  out <- B_arg1 + B_arg2

  # some GENERAL construction magic happens
  class(out) <- "ClassB"

  return(out)
}

My actual use case is a bit more complicated; I'm trying to use constructor and check.ClassName() methods to shoehorn type validation into S3.

Motivation

Obviously, I'd love to avoid the duplication in the general part of the above constructor functions, so a function factory that could be used like so would be nice:

ClassA <- produce_class_constructor(classname = "ClassA", fun = function(A_arg1, A_arg2) {return(list(A_arg1, A_arg2))})

This should, ideally, yield the exact same function as the above manually constructed ClassA function, with the general part factored out. (The general part in my real use case is quite a bit longer).

Caveat: It's crucial that the factory-produced ClassA() constructor retain the argument names (here A_arg1, A_arg2), so that documentation and autocomplete can guide the user in construction.

I want to use best functional programming (FP) practices to solve this problem.

I've previously asked about doing this via a simple closure, but I know wonder whether doing this via function operators might not be a better way, where I simply wrap (or compose) the general operators around the particular pieces for each class to yield a constructor function for each class.

In pseudocode, I'd like to design something like this:

produce_class_constructor <- function(classname, fun) {
  class_constructor_func <- validate_class(append_class(classname, fun()))
}

where, append_class() makes the custom fun also append some class, and validate_class() in turn also makes the custom fun run checks on its results.

I could then use this constructor function factor to elegantly create, say, the above ClassA():

    ClassA <- produce_class_constructor(classname = "ClassA", fun = function(A_arg1, A_arg2) {return(list(A_arg1, A_arg2))})

Attempt

Here's my simple (failing) attempt.

# simple helper to append classes
append_class <- function(obj, classname) {
  class(obj) <- append(class(obj), classname)
  return(obj)
}

produce_class_constructor <- function(classname, fun) {
  force(fun)  # not sure what this does, but seems to be necessary as per http://adv-r.had.co.nz/Function-operators.html

  # all the GENERAL construction magic from above now happens here
  class_constructor_fun <- function(...) {
    append_class(classname, fun(...))
  }
  formals(class_constructor_fun) <- formals(fun)  # this is necessary to carry over argument names

  return(class_constructor_fun)
}

ClassB <- produce_class_constructor(classname = "ClassB", fun = function(B_arg1, B_arg2) {
  # this is the class-SPECIFIC construction magic from above
  out <- B_arg1 + B_arg2
  return(out)
})

ClassB(B_arg1 = 1, B_arg2 = 2)  # this fails

The good news is that the resulting ClassB() has named arguments.

The bad news is that they don't get passed along, and the whole thing fails with:

 Error in append(class(obj), classname) : 
  '...' used in an incorrect context 

Question(s)

  1. Is this a sound "point-free" FP design and an ok/good idea generally?
  2. If yes, how can I fix my attempt? (= make the "inner" function aware of its arguments)
  3. If not, what would be the "proper" FP way of doing this?
  4. (Bonus): Is there already a package out there that shoehorns this kind of validation and rigour into S3? (other OOs are not an option and scare my pants off).
0

There are 0 answers