[r] 함수에서 명시 적으로 return 호출
얼마 전에 R 핵심 팀의 Simon Urbanek에 의해 책망 을 받았습니다 (사용자가 return
함수 끝에서 명시 적으로 전화하도록 권장 합니다 (그의 의견은 삭제되었지만).
foo = function() {
return(value)
}
대신 그는 추천했다 :
foo = function() {
value
}
아마도 이와 같은 상황에서는 다음이 필요합니다.
foo = function() {
if(a) {
return(a)
} else {
return(b)
}
}
그의 의견 return
은 꼭 필요한 것이 아니라면 왜 전화 하지 않는지에 대한 약간의 설명을 밝혔 지만 이것은 삭제되었다.
내 질문은 : 왜 return
더 빨리 또는 더 나은 전화를하지 않는 이유는 무엇 입니까?
답변
질문 : 왜 (명시 적으로) 호출이 더 빠르거나 더 나은 호출을하지 않는가?
R 문서에는 그러한 가정을하는 진술이 없습니다.
메인 페이지 ‘기능’은 다음과 같이 말합니다.
function( arglist ) expr
return(value)
return을 호출하지 않고 더 빠릅니까?
모두 function()
와 return()
기본 기능이며 function()
자체도 포함하지 않고 마지막에 평가 값을 반환하는 return()
기능.
호출 return()
로 .Primitive('return')
인수로 마지막 값은 같은 일을하지만 더 하나의 호출을 필요로합니다. 따라서이 .Primitive('return')
호출 (불필요한 호출)은 추가 자원을 끌어들일 수 있습니다. 그러나 간단한 측정은 결과 차이가 매우 작으므로 명시 적 반환을 사용하지 않는 이유가 될 수 없음을 보여줍니다. 이 방법으로 선택한 데이터에서 다음 플롯이 생성됩니다.
bench_nor2 <- function(x,repeats) { system.time(rep(
# without explicit return
(function(x) vector(length=x,mode="numeric"))(x)
,repeats)) }
bench_ret2 <- function(x,repeats) { system.time(rep(
# with explicit return
(function(x) return(vector(length=x,mode="numeric")))(x)
,repeats)) }
maxlen <- 1000
reps <- 10000
along <- seq(from=1,to=maxlen,by=5)
ret <- sapply(along,FUN=bench_ret2,repeats=reps)
nor <- sapply(along,FUN=bench_nor2,repeats=reps)
res <- data.frame(N=along,ELAPSED_RET=ret["elapsed",],ELAPSED_NOR=nor["elapsed",])
# res object is then visualized
# R version 2.15
위의 그림은 플랫폼에서 약간 어둡게 보일 수 있습니다. 측정 된 데이터를 기반으로 반환 된 객체의 크기는 차이를 일으키지 않습니다. 반복 수 (확장 된 경우에도)는 매우 작은 차이를 만듭니다. 실제 단어로 실제 데이터와 실제 알고리즘을 사용하면 계산하거나 계산할 수 없습니다 스크립트가 더 빨리 실행됩니다.
return을 호출하지 않고 더 낫습니까?
Return
루틴을 끝내고 함수에서 벗어나 값을 반환하는 코드의 “잎”을 명확하게 디자인하는 데 유용한 도구입니다.
# here without calling .Primitive('return')
> (function() {10;20;30;40})()
[1] 40
# here with .Primitive('return')
> (function() {10;20;30;40;return(40)})()
[1] 40
# here return terminates flow
> (function() {10;20;return();30;40})()
NULL
> (function() {10;20;return(25);30;40})()
[1] 25
>
어떤 스타일을 사용하는지 프로그래머의 전략과 프로그래밍 스타일에 따라 다르며 필요하지 않은 return ()을 사용할 수 없습니다.
R 코어 프로그래머는 두 가지 접근 방식을 모두 사용합니다. 기본 함수의 소스에서 찾을 수 있으므로 명시 적 return ()을 사용하거나 사용하지 않습니다.
많은 경우 return () 만 사용되며 (인수 없음) 경우에 따라 함수를 중지하기 위해 NULL을 반환합니다.
R을 사용하는 표준 사용자 또는 분석가가 실제 차이를 볼 수 없기 때문에 더 나은지 확실하지 않습니다.
내 의견은 질문은 다음과 같아야한다는 것입니다 : R 구현에서 오는 명시 적 수익을 사용하는 데 위험이 있습니까?
또는, 어쩌면 더 나은 사용자 쓰기 기능 코드를 항상 확인해야합니다 의 효과가 무엇입니까 하지 명시 적으로 반환을 사용하여 (또는 코드 브랜치의 마지막 잎으로 반환 될 객체를 배치) 기능 코드는?
답변
모두가 동의하면
return
함수 본문 끝에 필요하지 않습니다- 사용하지 않는
return
것이 조금 더 빠릅니다 (@Alan의 테스트에 따르면 4.3 마이크로 초 대 5.1)
return
함수 끝에서 모두 사용 을 중단해야 합니까? 나는 확실히하지 않을 것이며 왜 그런지 설명하고 싶습니다. 다른 사람들이 내 의견을 나누면 듣고 싶습니다. OP에 대한 정답이 아니라 긴 주관적인 의견과 비슷한 경우 사과드립니다.
return
폴이 지적했듯이 함수를 사용하지 않을 때의 주요 문제 는 함수 본문에 필요할 수있는 다른 장소가 있다는 것입니다. 그리고 return
함수 중간 어딘가에 사용해야한다면 모든 return
진술을 명시 적으로 작성 하지 않는 이유는 무엇입니까? 나는 일관성이없는 것을 싫어한다. 또한 코드가 더 잘 읽는다고 생각합니다. 함수를 스캔하고 모든 종료점과 값을 쉽게 볼 수 있습니다.
바울은이 예를 사용했습니다.
foo = function() {
if(a) {
return(a)
} else {
return(b)
}
}
불행히도, 다음과 같이 쉽게 다시 쓸 수 있다고 지적 할 수 있습니다.
foo = function() {
if(a) {
output <- a
} else {
output <- b
}
output
}
후자의 버전은 함수 당 하나의 리턴 문을 주장하는 일부 프로그래밍 코딩 표준을 준수합니다. 더 좋은 예는 다음과 같습니다.
bar <- function() {
while (a) {
do_stuff
for (b) {
do_stuff
if (c) return(1)
for (d) {
do_stuff
if (e) return(2)
}
}
}
return(3)
}
단일 return 문을 사용하여 다시 작성하는 것이 훨씬 더 어려울 수 break
있습니다. 전파하기 위해서는 여러 개의 변수와 복잡한 부울 변수 시스템 이 필요 합니다. 이 모든 것이 단일 반환 규칙이 R과 잘 어울리지 않는다고 말하면 return
함수 본문의 일부 위치에서 사용해야 할 경우 일관성이 있고 어디에서나 사용해야합니까?
속도 인수가 유효한 것으로 생각하지 않습니다. 실제로 무언가를 수행하는 함수를 볼 때 0.8 마이크로 초의 차이는 아무 것도 아닙니다. 내가 볼 수있는 마지막 것은 타이핑이 적지 만 게으르지 않다는 것입니다.
답변
이것은 흥미로운 토론입니다. @flodel의 예가 훌륭하다고 생각합니다. 그러나 기능적 코딩 스타일 대신 명령return
을 사용할 때 의미 가있는 내 요점 (@koshke가 주석에서 언급 함)을 보여 줍니다 .
요점은 아니지만 foo
다음과 같이 다시 작성했을 것입니다 .
foo = function() ifelse(a,a,b)
기능적 스타일은 값 저장과 같은 상태 변경을 피 output
합니다. 이 스타일에서는 return
제자리에 있지 않습니다. foo
수학 함수처럼 보입니다.
@flodel에 동의합니다. 복잡한 부울 변수 시스템을 사용하면 bar
덜 명확하고 무의미합니다 return
. 진술에 bar
매우 적합 하게 만드는 return
것은 명령형으로 작성되었다는 것입니다. 실제로, 부울 변수는 기능적 스타일에서 피한 “상태”변경을 나타냅니다.
bar
단순한 의사 코드이기 때문에 기능적 스타일 로 다시 작성하는 것은 실제로 어렵지만 아이디어는 다음과 같습니다.
e_func <- function() do_stuff
d_func <- function() ifelse(any(sapply(seq(d),e_func)),2,3)
b_func <- function() {
do_stuff
ifelse(c,1,sapply(seq(b),d_func))
}
bar <- function () {
do_stuff
sapply(seq(a),b_func) # Not exactly correct, but illustrates the idea.
}
while
가에 상태 변화에 의해 제어되기 때문에 루프는 다시 작성하기가 가장 어려울 것이다 a
.
호출로 인한 속도 손실 return
은 무시할 만하지 만 return
기능적 스타일 을 피하고 다시 작성하여 얻는 효율성 은 종종 막대합니다. 새로운 사용자에게 사용을 중지하라고 return
해도 도움이되지 않지만 기능적인 스타일로 안내하는 것이 좋습니다.
@Paul return
은 루프의 다른 지점에서 함수를 종료하려는 경우가 많으므로 명령형 스타일이 필요합니다. 기능적 스타일은 루프를 사용하지 않으므로 필요하지 않습니다 return
. 순전히 기능적인 스타일에서 최종 호출은 거의 항상 원하는 반환 값입니다.
파이썬에서 함수에는 return
문장이 필요합니다 . 그러나 기능적 스타일로 기능을 프로그래밍 한 경우 기능 return
이 끝날 때 하나의 명령문 만있을 수 있습니다.
다른 StackOverflow 포스트의 예제를 사용하여 TRUE
주어진 모든 값의 x
길이가 홀수 인 경우 반환되는 함수를 원한다고 가정합니다 . 우리는 두 가지 스타일을 사용할 수 있습니다 :
# Procedural / Imperative
allOdd = function(x) {
for (i in x) if (length(i) %% 2 == 0) return (FALSE)
return (TRUE)
}
# Functional
allOdd = function(x)
all(length(x) %% 2 == 1)
기능적 스타일에서 반환되는 값은 자연스럽게 함수의 끝에 해당합니다. 다시 한 번 더 수학 함수처럼 보입니다.
@GSee 여기에 설명 된 경고 ?ifelse
는 확실히 흥미롭지 만, 함수 사용을 설득하려고하지는 않습니다. 실제로 ifelse
자동으로 함수를 벡터화하는 이점이 있습니다. 예를 들어, 약간 수정 된 버전을 생각해보십시오 foo
.
foo = function(a) { # Note that it now has an argument
if(a) {
return(a)
} else {
return(b)
}
}
때이 기능은 잘 작동 length(a)
1입니다하지만 당신은 다시 썼다 경우 foo
로ifelse
foo = function (a) ifelse(a,a,b)
이제 foo
모든 길이에서 작동합니다 a
. 사실, 그것은 a
행렬 일 때도 작동 합니다. test
문제가 아닌 벡터화에 도움이되는 기능 과 동일한 모양을 반환합니다 .
답변
return()
더 빠르지 않은 것 같습니다 …
library(rbenchmark)
x <- 1
foo <- function(value) {
return(value)
}
fuu <- function(value) {
value
}
benchmark(foo(x),fuu(x),replications=1e7)
test replications elapsed relative user.self sys.self user.child sys.child
1 foo(x) 10000000 51.36 1.185322 51.11 0.11 0 0
2 fuu(x) 10000000 43.33 1.000000 42.97 0.05 0 0
____ 편집__ _ __ _ __ _ __ _ __ _ ___
나는 다른 벤치 마크 ( benchmark(fuu(x),foo(x),replications=1e7)
) 로 진행 하고 결과가 반전됩니다 … 서버에서 시도 할 것입니다.
답변
마지막에 명시 적으로 ‘return’을 넣지 않는 문제는 메소드 끝에 추가 명령문을 추가하면 갑자기 리턴 값이 잘못된다는 것입니다.
foo <- function() {
dosomething()
}
이 값을 반환합니다 dosomething()
.
이제 다음 날에 와서 새 줄을 추가하십시오.
foo <- function() {
dosomething()
dosomething2()
}
우리는 코드가 다음의 값을 반환하기를 원했습니다. dosomething()
더 이상 그렇지 않습니다.
명백한 수익으로, 이것은 정말로 명백해집니다.
foo <- function() {
return( dosomething() )
dosomething2()
}
이 코드에 이상한 것이 있다는 것을 알 수 있습니다.
foo <- function() {
dosomething2()
return( dosomething() )
}
답변
내 질문은 : 왜
return
빨리 전화하지 않는
return
R의 (기본) 함수 이기 때문에 더 빠릅니다. 즉, 코드에서 함수를 사용하면 함수 호출 비용이 발생합니다. 이것을 대부분의 다른 프로그래밍 언어와 비교하십시오.return
키워드이지만 함수 호출이 아닌 . 런타임 코드 실행으로 변환되지 않습니다.
즉, 이러한 방식으로 원시 함수를 호출하는 것은 R에서 매우 빠르며 호출 return
은 약간의 오버 헤드가 발생합니다. 이것은 생략에 대한 주장이 아닙니다 return
.
또는 더 나은, 따라서 바람직합니까?
그것을 사용할 이유 가 없기 때문에 .
중복되어 유용 하지 않기 때문에 중복을 입니다.
명확하게하기 위해 : 중복성 은 때때로 유용 할 수 있습니다 . 그러나 대부분의 중복성은 이런 종류가 아닙니다. 대신 정보를 추가하지 않고 시각적 혼란 을 더하는 종류 입니다. 필러 단어 또는 차트 정크 와 같은 프로그래밍입니다. ).
주석은 코드가 이미 표현한 것을 역설하기 때문에 일반적으로 잘못된 중복으로 인식되는 설명 주석의 다음 예를 고려하십시오.
# Add one to the result
result = x + 1
사용 return
R이 있기 때문에 R에서 것은 같은 범주에 빠진다 함수형 프로그래밍 언어 및 R에 모든 함수 호출에 값이 . 이것은이다 근본적인 R.의 자산 그리고 당신은 (모든 함수 호출을 포함하여) 모든 표현에 값이 있다는 관점에서 R 코드를 참조하면, 질문은 다음이된다 : “왜 해야 내가 사용 return
?” 기본값은 사용하지 않기 때문에 긍정적 인 이유가 있어야합니다.
이러한 긍정적 인 이유 중 하나는 가드 절 에서와 같이 함수에서 조기 종료를 알리는 것입니다 .
f = function (a, b) {
if (! precondition(a)) return() # same as `return(NULL)`!
calculation(b)
}
유효하고 중복되지 않는의 사용입니다 return
. 그러나 이러한 가드 절은 다른 언어와 비교하여 R에서는 드물며 모든 표현식에 값이 있으므로 정규식 if
에는 다음이 필요하지 않습니다 return
.
sign = function (num) {
if (num > 0) {
1
} else if (num < 0) {
-1
} else {
0
}
}
다음 f
과 같이 다시 작성할 수도 있습니다 .
f = function (a, b) {
if (precondition(a)) calculation(b)
}
… if (cond) expr
와 같은 곳if (cond) expr else NULL
.
마지막으로, 나는 세 가지 일반적인 이의를 제기하고 싶습니다 :
-
어떤 사람들은
return
“이 함수는 값을 반환합니다”라는 신호를 보내므로 명확성 을 더 한다고 주장합니다 . 그러나 위에서 설명한 것처럼 모든 함수는 R로 무언가return
를 반환합니다. 값을 반환하는 마커로 생각 하는 것은 중복이 아니라 적극적으로 오도 됩니다. -
관련하여, Zen of Python 에는 항상 따라야 할 놀라운 지침이 있습니다.
암시적인 것보다 명시적인 것이 좋습니다.
중복 제거가 어떻게
return
이를 위반 하지 않습니까? 함수형 언어에서 함수의 반환 값은 항상 명시 적이므로 마지막 식입니다. 이것은 다시 명시 성에 대한 동일한 주장입니다. 중복성 입니다.사실, 명확성을 원한다면 예외를 규칙에 강조 표시하십시오 . 의미있는 값을 반환 하지 않는 부수 함수 (예 :)는 부작용으로 인해 호출됩니다
cat
. R을 제외하고는return
이 경우 보다 마커가 더 좋습니다invisible
. 예를 들어save_results = function (results, file) { # … code that writes the results to a file … invisible() }
-
그러나 긴 기능은 어떻습니까? 반품 내용을 잃어 버리기 쉽지 않습니까?
두 가지 대답 : 첫째, 실제로는 아닙니다. 규칙은 분명하다 : 마지막 표현 합니다. 함수 은 그 값입니다. 추적 할 내용이 없습니다.
그러나 더 중요한 것은 긴 함수의 문제는 명확한
return
마커 가 없다는 것입니다 . 함수 의 길이입니다 . 긴 기능은 거의 (?) 거의 단일 책임 원칙을 위반하며 , 그렇지 않은 경우에도 가독성을 위해 분리되는 이점을 누릴 수 있습니다.
답변
return
트릭 이라고 생각 합니다. 일반적으로 함수에서 평가 된 마지막 표현식의 값은 함수의 값이되며이 일반적인 패턴은 여러 곳에서 찾을 수 있습니다. 다음은 모두 3으로 평가됩니다.
local({
1
2
3
})
eval(expression({
1
2
3
}))
(function() {
1
2
3
})()
어떤 return
일은 정말 아닌 반환 (이 함께 또는없이 완료) 값을하지만 불규칙한 방식으로 기능의 “탈옥”. 그런 의미에서, R에서 GOTO 문과 가장 비슷합니다 (단, 다음이 있습니다). 나는 return
매우 드물게 사용 하고 함수의 끝에서 결코 사용 하지 않습니다.
if(a) {
return(a)
} else {
return(b)
}
… 이것은 if(a) a else b
훨씬 더 읽기 쉽고 곱슬 머리가 적은 것으로 다시 작성할 수 있습니다 . 전혀 필요가 없습니다 return
. “반환”의 프로토 타입 사용 사례는 다음과 같습니다.
ugly <- function(species, x, y){
if(length(species)>1) stop("First argument is too long.")
if(species=="Mickey Mouse") return("You're kidding!")
### do some calculations
if(grepl("mouse", species)) {
## do some more calculations
if(species=="Dormouse") return(paste0("You're sleeping until", x+y))
## do some more calculations
return(paste0("You're a mouse and will be eating for ", x^y, " more minutes."))
}
## some more ugly conditions
# ...
### finally
return("The end")
}
일반적으로 많은 수익금이 필요하다는 것은 문제가 추악하거나 잘못 구조화되었음을 나타냅니다 .g
<>
return
실제로 작동하는 함수가 필요하지 않습니다.이 함수를 사용하여 평가할 표현식 세트를 분리 할 수 있습니다.
getout <- TRUE
# if getout==TRUE then the value of EXP, LOC, and FUN will be "OUTTA HERE"
# .... if getout==FALSE then it will be `3` for all these variables
EXP <- eval(expression({
1
2
if(getout) return("OUTTA HERE")
3
}))
LOC <- local({
1
2
if(getout) return("OUTTA HERE")
3
})
FUN <- (function(){
1
2
if(getout) return("OUTTA HERE")
3
})()
identical(EXP,LOC)
identical(EXP,FUN)