Реализация строгих перегруженных сигнатур методов, которые предотвращают злоупотребление - PullRequest
2 голосов
/ 03 апреля 2019

Я пытаюсь реализовать разные версии метода, которые имеют схожие, но разные требования к входным данным.Исходя из языков со статической и строгой типизацией, я пытаюсь написать эти методы таким образом, чтобы свести к минимуму возможность для целевых пользователей (это для пакета) использовать методы непреднамеренными способами.Разные версии метода имеют разное количество параметров, и я обнаружил, что поддержка версий метода с двумя или более параметрами позволяет вводить бессмысленную версию в версию метода, которая ожидает только один параметр.Вот тривиальный пример:

setGeneric(
  "foo",
  function(a, b) {
    standardGeneric("foo")
  })

setMethod(
  "foo",
  signature(a = "numeric"),
  function(a) {
    abs(a)
  })

setMethod(
  "foo",
  signature(a = "numeric", b = "numeric"),
  function(a, b) {
    abs(c(a, b))
  })

Он работает, как и ожидалось, со следующими входными данными (некоторые действительны, некоторые нет, и выдают ошибки, как и должны):

> foo(-1)
[1] 1

> foo(-1, -2)
[1] 1 2

> foo("cat")
 Error in (function (classes, fdef, mtable)  : 
  unable to find an inherited method for function ‘foo’ for signature ‘"character", "missing"’ 

> foo()
 Error in (function (classes, fdef, mtable)  : 
  unable to find an inherited method for function ‘foo’ for signature ‘"missing", "missing"’ 

> foo(-1, -2, "cat")
Error in foo(-1, -2, "cat") : unused argument ("cat")

Но тогда естьэто один из сценариев, в котором он не ведет себя так, как это должно быть приемлемо:

> foo(-1, "cat")
[1] 1

Он вызывает сигнатуру первого метода и игнорирует второй параметр.Это потенциально серьезная логическая ошибка для пользователей, которая является проблемой для меня, потому что мои целевые пользователи не являются компьютерными учеными;большинство из них не понимают, что такое логические ошибки, насколько они опасны или как их отслеживать. Есть ли способ в R настроить методы так, чтобы последний пример foo(-1, "cat") выдавал ошибку, а не создавал у пользователя впечатление, что все в порядке?

Примечание,что в то время как различные версии методов, над которыми я работаю, фундаментально связаны, фактические реализации для каждого из них очень разные.Я мог бы использовать функции с необязательными аргументами, но это потребовало бы нескольких проверок для запуска совершенно разных больших кусков кода.Я надеялся избежать этого, потому что это не особенно чисто или элегантно.

1 Ответ

3 голосов
/ 03 апреля 2019

Вы можете заставить его работать, если будете использовать специальную подпись "missing" следующим образом:

setGeneric(
  "foo",
  function(a, b) {
    standardGeneric("foo")
  })

setMethod(
  "foo",
  signature(a = "numeric", b = "missing"),
  function(a, b) {
    abs(a)
  })

setMethod(
  "foo",
  signature(a = "numeric", b = "numeric"),
  function(a, b) {
    abs(c(a, b))
  }
)

Проверка звонков:

foo(-1)
#[1] 1

foo(-1, -2)
#[1] 1 2

foo(-1, "cat")
#Error in (function (classes, fdef, mtable)  : 
#  unable to find an inherited method for function ‘foo’ for
#  signature ‘"numeric", "character"’ 

foo("cat")
# Error in (function (classes, fdef, mtable)  : 
#  unable to find an inherited method for function ‘foo’ for
#  signature ‘"character", "missing"’ 

Аналогично, foo() и foo(-1, -2, "cat") терпят неудачу, как и раньше.

Как вы сами заметили, если вы добавите в свои методы некоторые операторы print, вы увидите, что вызов foo(-1, "cat") отправляется вашему методу с одним аргументом. Причиной отсутствия ошибки является то, что обещание для аргумента b никогда не требуется и не оценивается. Я не очень хорошо разбираюсь в деталях правил диспетчеризации метода S4 в подобных случаях и в том, ожидалось ли это. Но в любом случае, в свете «отсутствующей» сигнатуры, я полагаю, что хорошей практикой является то, что аргументы методов всегда совпадают с аргументами универсального; и я уверен, что что-то вроде R CMD check будет жаловаться, если они не совпадают.

...