source

함수에서 반환을 명시적으로 호출할지 여부

manycodes 2023. 6. 25. 20:15
반응형

함수에서 반환을 명시적으로 호출할지 여부

얼마 에 사용자에게 명시적으로 전화를 걸 것을 추천한 것 때문에 코어 팀(내 생각에)의 사이먼 어반크에게 질책을 받았습니다.return함수의 끝에서 (그의 주석은 삭제되었지만):

foo = function() {
  return(value)
}

대신 그는 다음을 추천했습니다.

foo = function() {
  value
}

이러한 상황에서는 다음이 필요할 수 있습니다.

foo = function() {
 if(a) {
   return(a)
 } else {
   return(b)
 }
}

그의 논평은 왜 전화를 하지 않는지를 밝혀주었습니다.return엄격하게 필요한 것이 좋은 것이 아니라면, 그러나 이것은 삭제되었습니다.

다음과 같습니다:전화하지 죠?return더 빠르거나 더 낫고, 그래서 더 선호합니까?

질문:왜 (명시적으로) 반품을 더 빨리 또는 더 낫게 전화하는 것이 바람직하지 않습니까?

R 문서에는 그러한 가정을 하는 진술이 없습니다.
메인 페이지 ?'function'에는 다음과 같은 내용이 있습니다.

function( arglist ) expr
return(value)

리턴을 부르지 않고 더 빠른가요?

둘다요.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

Function elapsed time comparison

위의 그림은 플랫폼에서 약간 다를 수 있습니다.측정된 데이터를 기준으로 볼 때 반환된 개체의 크기는 아무런 차이가 없으며 반복 횟수(스케일링된 경우에도)는 매우 작은 차이를 보입니다. 즉, 실제 데이터와 실제 알고리즘으로는 계산할 수 없거나 스크립트가 더 빨리 실행되도록 할 수 없습니다.

리턴을 부르지 않는 게 낫습니까?

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
> 

어떤 스타일을 사용하느냐는 프로그래머의 전략과 프로그래밍 스타일에 따라 다르며, 필요하지 않기 때문에 리턴()을 사용할 수 없습니다.

코어 프로그래머는 '베이스' 함수의 소스에서 찾을 수 있는 것처럼 명시적 반환()이 있는 경우와 없는 경우 모두 접근법을 사용합니다.

종종 함수를 조건부로 중지하는 경우 NULL을 반환하는 반환(인수 없음)만 사용됩니다.

R을 사용하는 표준 사용자 또는 분석가는 실제 차이를 볼 수 없기 때문에 더 나은지 확실하지 않습니다.

제 의견은 다음과 같은 질문이 있어야 한다는 것입니다: R 구현에서 오는 명시적인 수익을 사용하는 데 위험이 있습니까?

또는 더 나은 방법은 사용자가 함수 코드를 작성할 때 항상 다음과 같이 묻는 것입니다.함수 코드에서 명시적 반환(또는 반환할 객체를 코드 분기의 마지막 리프로 배치)을 사용하지 않을 경우 어떤 효과가 있습니까?

모두가 동의한다면,

  1. return함수의 .
  2. 사용하지 않는return 4 대 )보다.

우리 모두 그만 사용해야 할까요?return함수의 끝에?저는 당연히 그렇게 하지 않을 것이고, 그 이유를 설명하고 싶습니다.저는 다른 사람들이 제 의견을 공유하는지 듣고 싶습니다.그리고 OP에 대한 솔직한 답변이 아니라 긴 주관적인 코멘트에 가깝다면 사과드립니다.

사용하지 않는 것에 대한 나의 주요 문제return즉, 폴이 지적한 것처럼, 함수의 몸 안에 당신이 필요로 하는 다른 장소가 있다는 것입니다.그리고 만약 당신이 강제로 사용한다면.return기능의 에, 것을 보는 것은 어떨까요?return명시적인 진술?저는 일관성이 없는 게 싫어요.또한 코드가 더 잘 읽힙니다. 함수를 스캔하고 모든 종료 지점과 값을 쉽게 볼 수 있습니다.

Paul은 다음 예를 사용했습니다.

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)
}

이는 단일 반환문을 사용하여 다시 작성하는 것이 훨씬 어려울 것입니다. 여러 개가 필요할 것입니다.breaks와 그것들을 전파하기 위한 복잡한 부울 변수 시스템.은 단일 모 것 단 일 에 반 작 지 않 의 것 는 합 미 니 다 을 다 는 하 동 든 잘 은 이 서 환 규 칙 이 ▁does ▁well ▁r 니 다 ▁all ▁return ▁rule ▁with 합 ▁say ▁play ▁the ▁single ▁that ▁not 의 ▁to ▁this 미 모 든 것 을그래서 만약 당신이 사용할 필요가 있다면.return당신의 기능 신체의 일부 장소에서, 일관성을 유지하고 모든 곳에서 그것을 사용하는 것은 어떻습니까?

저는 속도 논쟁이 타당하지 않다고 생각합니다.0.8마이크로초의 차이는 실제로 무언가를 하는 기능을 보기 시작할 때 아무것도 아닙니다.내가 마지막으로 볼 수 있는 것은 타이핑은 덜하지만 야, 나는 게으르지 않아요.

이것은 흥미로운 토론입니다.저는 @flodel의 예가 훌륭하다고 생각합니다.하지만 기능적 코딩 스타일 대신 명령어를 사용할 때 의미가 있는 제 요점을 설명한다고 생각합니다(그리고 @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아마도 도움이 되지 않겠지만, 기능적인 스타일로 안내하는 것이 효과가 있을 것입니다.


@1987return명령형에서는 루프의 다른 지점에서 함수를 종료하려는 경우가 많기 때문에 필요합니다.인 스타일은 루프를 를 사용할 필요가 없습니다.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)

함수 스타일에서 반환할 값은 함수의 끝에 자연스럽게 들어갑니다.다시 말하지만, 이것은 수학적 함수에 더 가깝습니다.

@GSE에서 한 입니다.?ifelse확실히 흥미롭긴 하지만, 그들이 그 기능의 사용을 만류하려는 것은 아니라고 생각합니다. 실은.ifelse함수를 자동으로 벡터화할 수 있는 장점이 있습니다.예를 들어 의 약간 수정된 버전을 생각해 보십시오.foo:

foo = function(a) { # Note that it now has an argument
 if(a) {
   return(a)
 } else {
   return(b)
 }
}

이 기능은 다음과 같은 경우에 잘 작동합니다.length(a) 1을 . 하지만 만약 당신이 다시 쓴다면.fooifelse

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)로 진행합니다.benchmark(fuu(x),foo(x),replications=1e7) 결가뒤었습니다뀌바과다▁)니습)었▁is.서버에 접속해 보겠습니다.

다음과 같습니다:전화하지 죠?return 빠른

더 빠릅니다. 왜냐하면.return는 R의 (기본) 함수로, 코드에서 이 함수를 사용하면 함수 호출 비용이 발생합니다.을 대부분의 .return는 키워드이지만 함수 호출이 아닙니다. 런타임 코드 실행으로 변환되지 않습니다.

그렇긴 하지만, 이런 방식으로 원시 함수를 호출하는 것은 R에서 꽤 빠르며, 호출은return아주 적은 비용이 듭니다.이것은 누락에 대한 주장이 아닙니다.return.

아니면 더 낫고, 그래서 더 좋습니까?

사용할 이유가 없기 때문입니다.

중복성이 있고 유용한 중복성을 추가하지 않기 때문입니다.

분명한 것은 중복이 때때로 유용할 수 있다는 것입니다.하지만 대부분의 중복성은 이런 종류가 아닙니다.대신 정보를 추가하지 않고 시각적으로 혼란을 가중시키는 종류입니다. 즉, 채우기 단어나 차트 정크와 동일한 프로그래밍입니다.

다음 설명 주석의 예를 생각해 보십시오. 이 주석은 코드가 이미 표현한 것을 단지 바꿔치기하기 때문에 일반적으로 불량 중복성으로 인식됩니다.

# Add one to the result
result = x + 1

용사를 합니다.returnR에서 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.

마지막으로 세 가지 일반적인 반대 의견을 미리 말씀드리겠습니다.

  1. 어떤 사람들은 사용하는 것이라고 주장합니다.return는 "이 함수가 값을 반환합니다"라는 신호를 보내므로 명확성을 추가합니다.그러나 위에서 설명한 바와 같이, 모든 함수는 R로 무언가를 반환합니다.을 생각하며return값을 반환하는 마커는 중복될 뿐만 아니라, 적극적으로 오해를 불러일으킵니다.

  2. 이와 관련하여, 파이썬의 Zen은 항상 따라야 하는 놀라운 가이드라인을 가지고 있습니다.

    명시적인 것이 암시적인 것보다 낫습니다.

    중복제거는 어떻게 가능합니까?return이것을 위반하지 않습니까?함수 언어에서 함수의 반환 값은 항상 명시적이기 때문에 함수의 마지막 표현입니다.이는 명시성 대 중복성에 대한 동일한 주장입니다.

    실제로 명시성을 원하는 경우 이를 사용하여 규칙에 대한 예외를 강조합니다. 의미 있는 값을 반환하지 않는 함수를 표시합니다. 이 함수는 부작용(예:catR이 다음보다 더 나은 마커를 갖는 것을 제외하고는return이 경우: .예를 들어, 나는 글을 쓸 것입니다.

    save_results = function (results, file) {
        # … code that writes the results to a file …
        invisible()
    }
    
  3. 하지만 긴 기능은 어떨까요?무엇이 반환되는지 추적하기가 쉽지 않을까요?

    두 가지 답: 첫째, 사실은 그렇지 않습니다.규칙은 명확합니다. 함수의 마지막 표현식은 값입니다.추적할 것이 없습니다.

    하지만 더 중요한 것은, 긴 기능의 문제는 명시적이지 않다는 것이 아닙니다.return마커함수의 길이입니다.긴 함수는 항상 단일 책임 원칙을 위반하며, 그렇지 않은 경우에도 가독성을 위해 분리되어 사용할 수 있습니다.

끝에 '반환'을 명시적으로 두지 않는 문제는 메소드 끝에 추가 문을 추가하면 반환 값이 갑자기 잘못된다는 것입니다.

foo <- function() {
    dosomething()
}

은 다값을 반니다합의 을 반환합니다.dosomething().

이제 다음 날에 와서 새 줄을 추가합니다.

foo <- function() {
    dosomething()
    dosomething2()
}

가 우는우리코값드반원을다했니의 을 반환하기를 .dosomething()하지만 그 대신 더 이상 그렇지 않습니다.

명확한 수익률을 통해 이는 매우 명백해집니다.

foo <- function() {
    return( dosomething() )
    dosomething2()
}

이 코드에 이상한 점이 있음을 확인하고 수정할 수 있습니다.

foo <- function() {
    dosomething2()
    return( dosomething() )
}

는 생난다를 합니다.return. -- 이됩니다.일반적으로 함수에서 계산된 마지막 식의 값은 함수의 값이 됩니다. 이 일반적인 패턴은 여러 곳에서 볼 수 있습니다.은 모두 33으로 합니다.

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훨씬 더 읽기 쉽고 덜 곱슬곱슬한 것.필요 없음returnpreturn▁of▁▁of▁제가 "반환"을 사용한 전형적인 사례는 다음과 같습니다.

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")
   }

일반적으로, 많은 수익에 대한 필요성은 문제가 추악하거나 구조가 잘못되었다는 것을 시사합니다.

[편집]

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)

여기서 중복성에 대한 논쟁이 많이 제기되었습니다.내 생각에는 그것은 생략하기에 충분한 이유가 아닙니다.return()중복성이 자동으로 나쁜 것은 아닙니다.전략적으로 사용할 경우, 중복성은 코드를 더 명확하게 하고 더 유지 관리할 수 있게 합니다.

이 예를 고려해 보십시오.함수 매개변수에는 종종 기본값이 있습니다.따라서 기본값과 동일한 값을 지정하는 것은 중복됩니다.내가 기대하는 행동이 분명하다는 것만 빼면요.기본값이 무엇인지 알려주기 위해 기능 메뉴 페이지를 열 필요가 없습니다.그리고 미래 버전의 함수가 기본값으로 변경될 것에 대해 걱정할 필요가 없습니다.

통화에 대한 성능 저하는 무시할 수 있습니다.return()(다른 사람들이 여기에 게시한 벤치마크에 따르면) 옳고 그름보다는 스타일로 귀결됩니다.어떤 것이 "잘못된" 것이 되려면, 명백한 단점이 있어야 하며, 여기에 포함하거나 누락하는 것을 만족스럽게 보여준 사람은 아무도 없습니다.return()일관된 단점이 있습니다.사례별로 매우 다양하고 사용자별로 다릅니다.

이것이 제가 이 문제에 대해 서 있는 이유입니다.

function(){
  #do stuff
  ...
  abcd
}

위의 예와 같이 "orphan" 변수가 불편합니다.이었다abcd내가 다 쓰지 못한 진술서의 일부가 될 것인가요?내 코드에 있는 스플라이스/편집의 잔여물이며 삭제해야 합니까?실수로 다른 곳에서 붙여넣거나 옮겼습니까?

function(){
  #do stuff
  ...
  return(abdc)
}

대조적으로, 이 두 번째 예는 어떤 사고나 불완전한 코드가 아닌 의도된 반환 값임을 분명히 합니다.나에게 이 중복은 절대로 쓸모없는 것이 아닙니다.

물론 기능이 끝나고 작동하면 반품을 제거할 수 있습니다.것이고, 에는 만그제것불추단며계이가한필없, 제로는포것더다습니쓸가모보함을 포함하는 것보다 더 가 없습니다.return()애당초

그렇다고는 해도, 나는 사용하지 않습니다.return()이름 없는 짧은 원라이너 함수입니다.거기서 그것은 함수 코드의 많은 부분을 차지하기 때문에 대부분 코드를 읽기 어렵게 만드는 시각적 혼란을 야기합니다.그러나 공식적으로 정의되고 명명된 더 큰 기능의 경우에는 이 기능을 사용하며 앞으로도 계속 사용할 것입니다.

return코드 가독성을 높일 수 있습니다.

foo <- function() {
    if (a) return(a)       
    b     
}

언급URL : https://stackoverflow.com/questions/11738823/explicitly-calling-return-in-a-function-or-not

반응형