217

しばらく前に、関数の最後で明示的に呼び出すことをユーザーに勧めたことで、R コア チームの Simon Urbanek (私は信じています) に叱責returnされました (ただし、彼のコメントは削除されました)。

foo = function() {
  return(value)
}

代わりに、彼は次のように勧めました。

foo = function() {
  value
}

おそらく、このような状況では必要です。

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

彼のコメントreturnは、厳密に必要な場合を除いて電話しないのは良いことである理由を明らかにしましたが、これは削除されました.

私の質問は次のとおりです。なぜ呼び出しがreturn速くないか、より良いのではないのでしょうか?

4

9 に答える 9

147

質問: return の呼び出しが (明示的に) 高速ではない、または優れているのはなぜですか?

R のドキュメントには、そのような仮定を行うステートメントはありません。
メインページ ?'function' には次のように書かれています:

function( arglist ) expr
return(value)

returnを呼び出さない方が速いですか?

function()とはどちらreturn()もプリミティブ関数であり、関数function()を含めなくても、それ自体が最後に評価された値を返しますreturn()

return()最後の値を引数としてasを呼び出す.Primitive('return')と、同じことを行いますが、もう 1 回呼び出す必要があります。この (しばしば) 不要な.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 を使用しない(またはコード分岐の最後のリーフとして返されるオブジェクトを配置する) ことによる影響は何ですか?

于 2012-08-06T19:19:23.583 に答える
111

誰もがそれに同意するなら

  1. return関数の本体の最後には必要ありません
  2. 使用しないreturn方がわずかに高速です(@Alanのテストによると、5.1マイクロ秒に対して4.3マイクロ秒)

return関数の最後で使用を停止する必要がありますか?私は確かにそうしません、そして私はその理由を説明したいと思います。他の人が私の意見を共有してくれるか聞いてみたいです。そして、それがOPに対する直接的な答えではなく、長い主観的なコメントのようなものである場合は、お詫び申し上げます。

使用しないことに関する私の主な問題returnは、Paulが指摘したように、関数の本体には、それを必要とする可能性のある他の場所があるということです。returnまた、関数の途中で使用する必要がある場合は、すべてのreturnステートメントを明示的にしてみませんか?私は一貫性がないのが嫌いです。また、コードの方が読みやすいと思います。関数をスキャンして、すべての出口点と値を簡単に確認できます。

Paulはこの例を使用しました:

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

残念ながら、次のように簡単に書き直すことができると指摘することができます。

foo = function() {
 if(a) {
   output <- a
 } else {
   output <- b
 }
output
}

後者のバージョンは、関数ごとに1つのreturnステートメントを推奨するいくつかのプログラミングコーディング標準にも準拠しています。より良い例は次のようになっていると思います。

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のsと、それらを伝播するためのブール変数の複雑なシステムが必要になります。これはすべて、シングルリターンルールがRでうまく機能しないことを意味します。したがって、関数の本体の一部の場所で使用する必要がある場合はreturn、一貫性を保ち、どこでも使用してみませんか?

速度の議論は有効なものではないと思います。実際に何かを実行する関数を見始めても、0.8マイクロ秒の違いは何もありません。私が見ることができる最後のことは、それがタイピングが少ないということです、しかしねえ、私は怠惰ではありません。

于 2012-08-10T04:21:14.800 に答える
28

これは興味深い議論です。@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もおそらく役に立たないでしょうが、機能的なスタイルに誘導することで利益が得られます.


@Paulreturnは、ループ内のさまざまなポイントで関数を終了したいことが多いため、命令型スタイルで必要です。機能的なスタイルはループを使用しないため、必要ありませんreturn。純粋に機能的なスタイルでは、ほとんどの場合、最後の呼び出しが目的の戻り値になります。

Python では、関数にはreturnステートメントが必要です。ただし、関数スタイルで関数をプログラミングした場合は、関数returnの最後に 1 つのステートメントしかない可能性があります。

TRUE別の StackOverflow 投稿の例を使用して、指定されたすべての値のx長さが奇数の場合に返される関数が必要だとしましょう。2 つのスタイルを使用できます。

# 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 のときに問題なく動作しますfooifelse

foo = function (a) ifelse(a,a,b)

fooの任意の長さで機能するようになりましたaa実際、が行列の場合でも機能します。と同じ形状の値を返すtestことは、ベクトル化に役立つ機能であり、問​​題ではありません。

于 2012-08-11T11:43:04.143 に答える
22

無い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)) に進むと、結果が逆になります... サーバーで試してみます。

于 2012-07-31T13:48:17.230 に答える
14

最後に明示的に 'return' を置かないことの問題は、メソッドの最後に追加のステートメントを追加すると、突然戻り値が間違っていることです:

foo <- function() {
    dosomething()
}

の値を返しますdosomething()

翌日、新しい行を追加します。

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

コードが の値を返すようにしたかったのですがdosomething()、そうではなくなりました。

明示的な return を使用すると、これは非常に明白になります。

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

このコードに奇妙な点があることがわかり、修正します。

foo <- function() {
    dosomething2()
    return( dosomething() )
}
于 2012-12-05T03:36:24.873 に答える
6

私はreturnトリックとして考えます。原則として、関数で評価された最後の式の値が関数の値になります。この一般的なパターンは多くの場所で見られます。以下はすべて 3 と評価されます。

local({
1
2
3
})

eval(expression({
1
2
3
}))

(function() {
1
2
3
})()

実際に値を返すreturnのではなく(これは値の有無にかかわらず行われます)、不規則な方法で関数を「ブレイクアウト」します。その意味では、R の GOTO 文に最も近いものです (break や next もあります)。私はめったに使用せず、関数の最後には使用しません。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")
   }

一般に、多くの返品が必要な場合は、問題が醜いか構造が悪いことを示唆しています。

[編集]

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)
于 2013-10-28T21:48:54.590 に答える