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 

これまでのところ、とても良い。 sumsumR間に明らかな違いはありません。これは、 returnが単なるオプションのキーワードであるという考えにつながる可能性があります。 しかし、手動でaddaddRadd 、両方のメソッドを少しリファクタリングしましょう。


 //  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のタイプが何であれ、それは同時にIntString還元可能でなければなりません。 そして、これらのクラスは両方ともfinalであり、 IntAnyValであるため、すべてがどこに行くかを知っています。


 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に、翻訳をもう少し正確にするいくつかの実用的なヒントをありがとう。



Source: https://habr.com/ru/post/J331138/


All Articles