Scalaでreturnを使用しないでください

今日は、 tpolecatというニックネームでおなじみのロバート・ノリスによる短い記事の翻訳に注目したいと思います。 この人は、 Doobieライブラリの作成者およびcatプロジェクト参加者としてScalaコミュニティでよく知られています。







Robertの出版物で、Robertはreturn



の使用がコードのセマンティクスにマイナスの影響を与える可能性があることについて語り、Scalaでのreturn



の実装のいくつかの興味深い機能に光を当てています。 リンクの著者のブログで元の記事を見つけることができます。







したがって、MartinがCourseraでコースを開始するたびに、人々は私たちの#scalaに登場して、なぜ返品のためにスタイルポイントを取得するのかを尋ねます。 したがって、ここに貴重なアドバイスがあります。







return



キーワードは、コンテキストで「オプション」または「暗黙」ではありません-プログラムの意味を変更するため、使用しないでください。

この小さな例を見てください:







 //         , //   . def add(n: Int, m: Int): Int = n + m def sum(ns: Int*): Int = ns.foldLeft(0)(add) scala> sum(33, 42, 99) res0: Int = 174 //   ,    return. def addR(n:Int, m:Int): Int = return n + m def sumR(ns: Int*): Int = ns.foldLeft(0)(addR) scala> sumR(33, 42, 99) res1: Int = 174
      
      





これまでのところ、とても良い。 sum



sumR



間に明らかな違いはありません。これは、 return



が単なるオプションのキーワードであるという考えにつながる可能性があります。 しかし、手動でadd



addR



add



、両方のメソッドを少しリファクタリングしましょう。







 //  add. def sum(ns: Int*): Int = ns.foldLeft(0)((n, m) => n + m) scala> sum(33, 42, 99) res2: Int = 174 //  . //  addR. def sumR(ns: Int*): Int = ns.foldLeft(0)((n, m) => return n + m) scala> sumR(33, 42, 99) res3: Int = 33 // ...
      
      





何...?!



要するに:







制御フローがreturn



式に達すると、現在の計算が停止され、returnが配置されているメソッドから即時に戻ります。

2番目の例では、 return



は匿名関数から値を返しません。内部にあるメソッドから値を返します。 別の例:







 def foo: Int = { val sumR: List[Int] => Int = _.foldLeft(0)((n, m) => return n + m) sumR(List(1,2,3)) + sumR(List(4,5,6)) } scala> foo res4: Int = 1
      
      





非ローカルリターン



return



呼び出しを含む機能オブジェクトが非ローカルで実行されると、計算が停止され NonLocalReturnControl[A]



例外を発生て結果が返されます 。 この実装の詳細は、特別な儀式なしで簡単に漏れます。







 def lazily(s: => String): String = try s catch { case t: Throwable => t.toString } def foo: String = lazily("foo") def bar: String = lazily(return "bar") scala> foo res5: String = foo scala> bar res6: String = scala.runtime.NonLocalReturnControl
      
      





Throwable



インターセプトが悪いThrowable



であると今すぐ誰かThrowable



私にThrowable



ている場合、悪いスタイルは実行フローを制御するために例外を使用していることを彼に伝えることができます。 標準ライブラリのbreakable



と呼ばれる愚かさは構造が似ており、returnのように使用すべきではありません。







別の例。 return



が、ネイティブメソッドが機能した後も存続するラムダ式にロックされている場合はどうなりますか? 喜んで、あなたはあなたが自由に使用する最初の試みで爆発する時限爆弾を持っています。







 scala> def foo: () => Int = () => return () => 1 foo: () => Int scala> val x = foo x: () => Int = <function0> scala> x() scala.runtime.NonLocalReturnControl
      
      





追加のボーナスは、 NonLocalReturnControl



から継承されるという事実であるため、この爆弾が作成された場所についての手がかりはありません。 クールなもの。







どの型がreturn



ますか?



return a



の構築でreturn a



returna



は、 return



が配置されているメソッドの結果の型と一致a



必要がありますが、 return a



自体は型です。 「それ以上の計算を停止する」という意味に基づいて、どのタイプを持っているかを推測する必要があります。 そうでない場合、ここに少しの啓発があります:







 def x: Int = { val a: Int = return 2; 1 } //  2
      
      





ご覧のとおり、タイプアナライザーは誓うものではないため、式のタイプが常にreturn a



のタイプと一致すると仮定できます。 動作しないはずの何かを記述して、この理論をテストしてみましょう。







 def x: Int = { val a: String = return 2; 1 }
      
      





うーん、また誓わない。 何が起こっているの? return 2



のタイプが何であれ、それは同時にInt



String



還元可能でなければなりません。 そして、これらのクラスは両方ともfinal



であり、 Int



AnyVal



であるため、すべてがどこに行くかを知っています。







 def x: Int = { val a: Nothing = return 2; 1 }
      
      





まさにそう、 Nothing



。 そして、あなたがNothing



たびに、向きを変えて逆方向に行くのが賢明でしょう。 Nothing



は無人であるため(このタイプの単一の値はありません)、 return



結果にはプログラム内の通常の表現もありません。 評価する場合、 Nothing



タイプの式は、無限ループに入るか、仮想マシンを終了するか、(例外として)他の場所に制御を転送する必要があります。







「実際、この例では、論理的には継続を引き起こすだけで、Schemeでこれを常に行っていますが、ここではまったく問題が発生しません」と考えただけです。 ここにあなたのためのクッキーがあります。 しかし、あなた以外の誰もがこれはおかしいと思っています。







リンクの透明度を返す



これは、いわば明らかです。 しかし、突然、これらの言葉が何を意味するのかを完全に認識しているわけではありません。 したがって、次のようなコードがある場合:







 def foo(n:Int): Int = { if (n < 100) n else return 100 }
      
      





それから、もしそれが参照的に透明であれば、私はこのような意味を変えずにそれを書き直す権利があります:







 def foo(n: Int): Int = { val a = return 100 if (n < 100) n else a }
      
      





もちろん、動作しません。returnを実行すると副作用が発生します。







しかし、 本当に必要な場合はどうなりますか?



必要ありません。 自分の意見では、メソッドをスケジュールより早くする必要があるという状況に陥った場合、実際にはコード構造をやり直す必要があります。 たとえば、これ







 //       , //     . def max100(ns: List[Int]): Int = ns.foldLeft(0) { (n, m) => if (n + m > 100) return 100 else n + m }
      
      





単純な末尾再帰を使用して書き換えることができます。







 def max100(ns: List[Int]): Int = { def go(ns: List[Int], a: Int): Int = if (a >= 100) 100 else ns match { case n :: ns => go(ns, n + a) case Nil => a } go(ns, 0) }
      
      





この変換は常に可能です。 言語からreturn



完全に削除しても、Scalaで記述できないプログラムの数は1つも増えません。 return



の無益さを受け入れるには、少し努力する必要があるかもしれませんが、結果として、制御フローの非局所的な遷移によって引き起こされる副作用を予測しようと頭を悩ますよりも、突然の戻りなしでコードを書く方がはるかに簡単であることがわかります。







翻訳者から:

校正のためのBortnikova Evgeniaに感謝します。 翻訳を明確にしてくれたfiregurafikuに感謝します。 Vlad Ledovskyに、翻訳をもう少し正確にするいくつかの実用的なヒントをありがとう。








All Articles