Cách khắc phục chính xác Cách thức để xác định các đối số tùy chọn trong các hàm R


165

Tôi quan tâm đến cách viết "hàm" chính xác với các đối số tùy chọn trong R. Theo thời gian, tôi tình cờ thấy một vài đoạn mã đi theo một tuyến đường khác ở đây và tôi không thể tìm thấy một vị trí (chính thức) thích hợp về chủ đề này.

Cho đến bây giờ, tôi đã viết các đối số tùy chọn như thế này:

fooBar <- function(x,y=NULL){
  if(!is.null(y)) x <- x+y
  return(x)
}
fooBar(3) # 3
fooBar(3,1.5) # 4.5

Hàm chỉ đơn giản trả về đối số của nó nếu chỉ xđược cung cấp. Nó sử dụng một NULLgiá trị mặc định cho đối số thứ hai và nếu đối số đó xảy ra là không NULL, thì hàm sẽ thêm hai số.

Ngoài ra, người ta có thể viết hàm như thế này (trong đó đối số thứ hai cần được chỉ định theo tên, nhưng người ta cũng có thể unlist(z)hoặc định nghĩa z <- sum(...)thay thế):

fooBar <- function(x,...){
  z <- list(...)
  if(!is.null(z$y)) x <- x+z$y
  return(x)
}
fooBar(3) # 3
fooBar(3,y=1.5) # 4.5

Cá nhân tôi thích phiên bản đầu tiên. Tuy nhiên, tôi có thể thấy tốt và xấu với cả hai. Phiên bản đầu tiên ít bị lỗi hơn, nhưng phiên bản thứ hai có thể được sử dụng để kết hợp một số tùy chọn tùy ý.

Có cách "chính xác" nào để chỉ định các đối số tùy chọn trong R không? Cho đến nay, tôi đã giải quyết theo cách tiếp cận đầu tiên, nhưng cả hai đôi khi có thể cảm thấy một chút "hacky".


Kiểm tra mã nguồn xy.coordsđể xem cách tiếp cận thường được sử dụng.
Carl Witthoft

5
Mã nguồn xy.coordsđược đề cập bởi Carl Witthoft l có thể được tìm thấy tại xy.coords
RubenLaguna

Câu trả lời:


129

Bạn cũng có thể sử dụng missing()để kiểm tra xem đối số có yđược cung cấp hay không :

fooBar <- function(x,y){
    if(missing(y)) {
        x
    } else {
        x + y
    }
}

fooBar(3,1.5)
# [1] 4.5
fooBar(3)
# [1] 3

5
Tôi thích thiếu tốt hơn. đặc biệt nếu bạn có nhiều giá trị mặc định NULL, bạn sẽ không có x = NULL, y = NULL, z = NULL trong tài liệu gói của bạn
rawr 6/215

5
@rawr missing()cũng biểu cảm hơn theo nghĩa là nó "nói lên ý nghĩa của nó". Thêm vào đó, nó cho phép người dùng vượt qua giá trị NULL, ở những nơi có ý nghĩa!
Josh O'Brien

30
Đối với tôi, có một nhược điểm lớn khi sử dụng bị thiếu theo cách này: khi lướt qua các đối số chức năng, bạn không còn có thể thấy đối số nào là bắt buộc và tùy chọn nào là tùy chọn.
hadley 7/2/2015

3
@param x numeric; something something; @param y numeric; **optional** something something; @param z logical; **optional** something something
rawr

4
missing()thật tồi tệ khi bạn muốn truyền các đối số từ hàm này sang hàm khác.
John Smith

55

Thành thật mà nói, tôi thích cách đầu tiên của OP thực sự bắt đầu với một NULLgiá trị và sau đó kiểm tra nó is.null(chủ yếu vì nó rất đơn giản và dễ hiểu). Nó có thể phụ thuộc vào cách mọi người sử dụng để mã hóa nhưng Hadley dường như hỗ trợis.null theo cách này:

Từ cuốn sách "Advanced-R" của Hadley Chương 6, Chức năng, tr.84 (để xem phiên bản trực tuyến tại đây ):

Bạn có thể xác định xem một đối số có được cung cấp hay không với hàm thiếu ().

i <- function(a, b) {
  c(missing(a), missing(b))
}
i()
#> [1] TRUE TRUE
i(a = 1)
#> [1] FALSE  TRUE
i(b = 2)
#> [1]  TRUE FALSE
i(1, 2)
#> [1] FALSE FALSE

Đôi khi bạn muốn thêm một giá trị mặc định không tầm thường, có thể mất vài dòng mã để tính toán. Thay vì chèn mã đó vào định nghĩa hàm, bạn có thể sử dụng thiếu () để tính toán có điều kiện nếu cần. Tuy nhiên, điều này làm cho khó biết được đối số nào là bắt buộc và tùy chọn nào là tùy chọn mà không đọc kỹ tài liệu. Thay vào đó, tôi thường đặt giá trị mặc định thành NULL và sử dụng is.null () để kiểm tra xem đối số có được cung cấp không.


2
Hấp dẫn. Điều đó nghe có vẻ hợp lý, nhưng bạn có bao giờ thấy mình bối rối về những đối số nào cho một hàm được yêu cầu và tùy chọn nào không? Tôi không chắc rằng tôi đã bao giờ thực sự đã có kinh nghiệm ...
Josh O'Brien

2
@ JoshO'Brien Tôi nghĩ rằng tôi không gặp vấn đề gì với phong cách mã hóa, nhưng ít nhất nó không bao giờ là vấn đề lớn có lẽ là do tài liệu hoặc đọc mã nguồn. Và đó là lý do tại sao tôi chủ yếu nói rằng đó thực sự là vấn đề của phong cách mã hóa mà bạn đã quen. Tôi đã sử dụng NULLcách này khá lâu và có lẽ đó là lý do tại sao tôi quen với nó hơn khi tôi thấy mã nguồn. Nó có vẻ tự nhiên hơn đối với tôi. Điều đó nói rằng, như bạn nói cơ sở R có cả hai cách tiếp cận như vậy, nó thực sự đi vào sở thích cá nhân.
LyzandeR

2
Đến bây giờ, tôi thực sự ước mình có thể đánh dấu hai câu trả lời là đúng bởi vì những gì tôi thực sự đạt được khi sử dụng cả hai is.nullmissingtùy thuộc vào ngữ cảnh và đối số được sử dụng để làm gì.
SimonG

5
Đó là ok @SimonG và cảm ơn :). Tôi đồng ý rằng cả hai câu trả lời đều rất hay và đôi khi chúng phụ thuộc vào ngữ cảnh. Đây là một câu hỏi rất hay và tôi tin rằng các câu trả lời cung cấp thông tin và kiến ​​thức rất tốt, đó là mục tiêu chính ở đây.
LyzandeR

24

Đây là những quy tắc của tôi:

Nếu các giá trị mặc định có thể được tính từ các tham số khác, hãy sử dụng các biểu thức mặc định như trong:

fun <- function(x,levels=levels(x)){
    blah blah blah
}

nếu không sử dụng bị thiếu

fun <- function(x,levels){
    if(missing(levels)){
        [calculate levels here]
    }
    blah blah blah
}

Trong trường hợp hiếm hoi mà bạn nghĩ rằng người dùng có thể muốn chỉ định một giá trị mặc định kéo dài toàn bộ phiên R, hãy sử dụnggetOption

fun <- function(x,y=getOption('fun.y','initialDefault')){# or getOption('pkg.fun.y',defaultValue)
    blah blah blah
}

Nếu một số tham số áp dụng tùy thuộc vào lớp của đối số đầu tiên, hãy sử dụng chung S3:

fun <- function(...)
    UseMethod(...)


fun.character <- function(x,y,z){# y and z only apply when x is character
   blah blah blah 
}

fun.numeric <- function(x,a,b){# a and b only apply when x is numeric
   blah blah blah 
}

fun.default <- function(x,m,n){# otherwise arguments m and n apply
   blah blah blah 
}

...Chỉ sử dụng khi bạn chuyển các tham số bổ sung sang chức năng khác

cat0 <- function(...)
    cat(...,sep = '')

Cuối cùng, nếu bạn chọn sử dụng ...mà không chuyển các dấu chấm vào một chức năng khác, hãy cảnh báo người dùng rằng chức năng của bạn đang bỏ qua mọi tham số không sử dụng vì nó có thể rất khó hiểu:

fun <- (x,...){
    params <- list(...)
    optionalParamNames <- letters
    unusedParams <- setdiff(names(params),optionalParamNames)
    if(length(unusedParams))
        stop('unused parameters',paste(unusedParams,collapse = ', '))
   blah blah blah 
}

tùy chọn phương pháp s3 cũng là một trong những điều đầu tiên tôi nghĩ
rawr

2
Nhìn lại, tôi đã trở nên yêu thích phương pháp gán của OP NULLtrong chữ ký hàm, vì nó thuận tiện hơn cho việc tạo các hàm theo chuỗi độc đáo.
Jthorpe

10

Có một số tùy chọn và không có tùy chọn nào là cách chính xác và không có tùy chọn nào thực sự không chính xác, mặc dù chúng có thể truyền thông tin khác nhau đến máy tính và cho người khác đọc mã của bạn.

Đối với ví dụ đã cho, tôi nghĩ tùy chọn rõ ràng nhất sẽ là cung cấp giá trị mặc định danh tính, trong trường hợp này hãy thực hiện một số thứ như:

fooBar <- function(x, y=0) {
  x + y
}

Đây là ngắn nhất trong số các tùy chọn được hiển thị cho đến nay và độ ngắn có thể giúp dễ đọc (và đôi khi thậm chí cả tốc độ trong thực thi). Rõ ràng rằng những gì đang được trả về là tổng của x và y và bạn có thể thấy rằng y không được đưa ra một giá trị mà nó sẽ là 0 mà khi thêm vào x sẽ chỉ dẫn đến x. Rõ ràng nếu một cái gì đó phức tạp hơn bổ sung được sử dụng thì sẽ cần một giá trị nhận dạng khác (nếu có tồn tại).

Một điều tôi thực sự thích về cách tiếp cận này là rõ ràng giá trị mặc định là gì khi sử dụng argshàm hoặc thậm chí nhìn vào tệp trợ giúp (bạn không cần phải cuộn xuống chi tiết, nó có ngay trong cách sử dụng ).

Hạn chế đến phương pháp này là khi giá trị mặc định là phức tạp (đòi hỏi nhiều dòng mã), sau đó nó có thể sẽ làm giảm khả năng đọc để cố gắng đưa tất cả những gì vào giá trị mặc định và missinghayNULL cách tiếp cận trở nên hợp lý hơn.

Một số khác biệt khác giữa các phương thức sẽ xuất hiện khi tham số được truyền cho hàm khác hoặc khi sử dụng hàm match.callhoặc sys.call.

Vì vậy, tôi đoán phương pháp "chính xác" phụ thuộc vào những gì bạn dự định làm với đối số cụ thể đó và thông tin bạn muốn truyền đạt tới người đọc mã của bạn.


7

Tôi có xu hướng thích sử dụng NULL vì sự rõ ràng của những gì được yêu cầu và những gì là tùy chọn. Một từ cảnh báo về việc sử dụng các giá trị mặc định phụ thuộc vào các đối số khác, như được đề xuất bởi Jthorpe. Giá trị không được đặt khi hàm được gọi, nhưng khi đối số được tham chiếu lần đầu tiên! Ví dụ:

foo <- function(x,y=length(x)){
    x <- x[1:10]
    print(y)
}
foo(1:20) 
#[1] 10

Mặt khác, nếu bạn tham chiếu y trước khi thay đổi x:

foo <- function(x,y=length(x)){
    print(y)
    x <- x[1:10]
}
foo(1:20) 
#[1] 20

Điều này hơi nguy hiểm, vì nó khiến bạn khó theo dõi những gì "y" đang được khởi tạo như thể nó không được gọi sớm trong chức năng.


7

Chỉ muốn chỉ ra rằng sinkhàm tích hợp có các ví dụ hay về các cách khác nhau để đặt đối số trong hàm:

> sink
function (file = NULL, append = FALSE, type = c("output", "message"),
    split = FALSE)
{
    type <- match.arg(type)
    if (type == "message") {
        if (is.null(file))
            file <- stderr()
        else if (!inherits(file, "connection") || !isOpen(file))
            stop("'file' must be NULL or an already open connection")
        if (split)
            stop("cannot split the message connection")
        .Internal(sink(file, FALSE, TRUE, FALSE))
    }
    else {
        closeOnExit <- FALSE
        if (is.null(file))
            file <- -1L
        else if (is.character(file)) {
            file <- file(file, ifelse(append, "a", "w"))
            closeOnExit <- TRUE
        }
        else if (!inherits(file, "connection"))
            stop("'file' must be NULL, a connection or a character string")
        .Internal(sink(file, closeOnExit, FALSE, split))
    }
}

1

Còn cái này thì sao?

fun <- function(x, ...){
  y=NULL
  parms=list(...)
  for (name in names(parms) ) {
    assign(name, parms[[name]])
  }
  print(is.null(y))
}

Vậy hãy thử đi:

> fun(1,y=4)
[1] FALSE
> fun(1)
[1] TRUE
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.