6

私は、scala の継続に関する複雑な型付けの問題に頭を悩ませようとしてきました。継続パッケージの参照ドキュメントを含め、私が見つけることができるすべての資料を読んでいます。私はそれをある程度理解していると思います。

それについての私の理解(および私の質問の一部)は、このプログラムによって最もよく要約できると思います。

package com.whatever;
import scala.util.continuations._;

object methods {

  /* The method takes an Int as its parameter.  Theoretically, at some point in the future,
   * it will return a Float to the remainder of the continuation.  This example does it
   * immediately but doesn't have to (for example it could be calling a network service
   * to do the transformation)
   * 
   * Float @cpsParam[Unit,Float] means that whatever part of the reset{} that is captured
   * as a closure should receive a Float and needn't return anything (would it be meaningful
   * if Unit were something else?)
   * 
   * The reason I have to return 0.toFloat is so the compiler can properly type the
   * method.  That zero will never go anywhere.  Is this a sign I'm doing it wrong?
   */
  def method1(param:Int): Float @cpsParam[Unit,Float] = shift { cb:(Float=>Unit) =>
    cb(param.toFloat); 
    0.toFloat;
  }

  /* This method is basically identical but returns a String instead of a Float (Again,
   * theoretically this would be done by a network service and cb would be called at some
   * point in the future.
   */
  def method2(param:Int): String @cpsParam[Unit,String] = shift { cb:(String=>Unit) =>
    cb(param.toString);
    ""
  }
}

object Main {
  def main(args:Array[String]):Unit = {
    reset {
      val f = methods.method1(5);
      println(f);
    }
  }
}

ちなみに、StackOverflow が scala をハイライトしないのは犯罪です!(私は訂正しました。実際にはかなり良い仕事をしますが、ライブプレビューではそうではありません)

私の質問は次のとおりです。

  1. 上記のプログラムのコメントから判断すると、scala の CPS についての私の理解に欠けているものは何ですか? Unitのように望んBでいない状況はあります@cpsParam[B,C]か?
  2. 上記のプログラムはコンパイルされ、機能します (出力され"5.0"ます)。しかし、私が混乱を引き起こしている現在直面している問題は、ブロックを変更してafterresetを呼び出そうとするときです。method2method1

(明らかに、リストの直後にコード ブロックを配置することはできません)

reset {
  val f = methods.method1(5);
  println(f);
  val s = methods.method2(42);
  println(s);
}

これを行うと (これは非常に単純なことのように思えます)、リセット時に次のコンパイラ エラーが発生します (これは scala 2.10 Milestone 2 です)。

illegal answer type modification: scala.util.continuations.cpsParam[Unit,Float] andThen scala.util.continuations.cpsParam[Unit,String]

これは、「最初のシフトは Float を返し、2 番目のシフトは String を返しますが、それはできない」という意味だと解釈します。これは正確ですか?同じ戻り値の型でない限り、CPS を使用して 2 つ (またはそれ以上) のことを連続して実行できないということですか? それは一種の深刻な制限のように思えるからです。私は、1) これを可能にする何かが欠けているか、B) CPS でそれが不可能である明白な理由が欠けている、のいずれかだと思います。しかし、それはどれですか?

scala の CPS を理解するために、ポスドクの学生である必要はないと感じ始めています。しかし、私は確かにまだそこにいません。

4

1 に答える 1

4

この質問をした後、私はさらに多くの調査を行い、今では自分の質問に答えることができると思います(これが偽物ではないことを願っています)。

問題を理解するのに役立った3つのことを行いました。scalaの継続に問題がある人は、次の手順に従うとよいと思います。

  1. Scalaの継続についての元の学術論文を読んでください。それは非常に乾燥していて、ほとんどが狂信者のグループのぎこちない怒りだと思いますが、コンパイラが継続駆動型コードをどのように変換するか、そしてそれがタイピングと純度の問題をどのように変換するかについての洞察を与えるという点でも非常に役立ちますそうすることで直面します。
  2. コールバック受け渡しスタイルでコードを書き直します。 これは、継続のフローとそのタイプで何が起こっているのかを実際に把握するために実行できる最も重要なことの1つです。
  3. 調べてください。つまり、の型アノテーションを実際に調べshiftて、それが何をしているのかに注意を払うということです。これは私が持っていたエピファニーにあなたを駆り立てます。

私の場合、@cpsParamsとcbパラメータをshiftすべて間違って入力していました。私が間違っていることをどのように理解したかを説明します。これにより、私と同じくらい愚かな人なら誰でも同じ手順を実行でき、継続コンパイラがそれらを狂わせているときに洞察を得ることができます。

ステップ1

上記の論文を読みました。約12回。私はまだそれをほとんど理解していません。しかし、私が理解していることは非常に役に立ちます。

ステップ2

resetコールバック受け渡しスタイルでブロックを書き直し、の代わりに、各メソッドに、ブロックの残りの部分を実行する関数を受け取るというshift2番目のパラメーターが呼び出されたように見せかけました。cbこの後のリセットブロックは次のようになります。

  methods.method1(5, {f: Int => {
    println(f);
    methods.method2(42, {s: String => {
        println(s);
    });
  });

何が起こっているのか分かりますか?そのため、ブロックしているように見えるコードを記述する代わりに、継続を明示的に区切り、各メソッドに関数として渡します。したがって、これらの状況のそれぞれについて、匿名のコールバック関数のそれぞれが何も返す必要がないことが明らかになります。実際、両方を返す必要があります。そうしないと、Unit渡されるメソッドの戻り型が汚染されます。これはコンパイラが私に伝えようとしていたことだと思います(私は間違っているかもしれませんが)

これmethod1が私のコールバックスタイルのプログラムのように見える必要があります

   def method1(param:Int, cb:(Float=>Unit)):Unit = {
     cb(param.toFloat);
   }

method2似ていますが、かかります(String=>Unit)。これで、私のメソッドも戻る必要があることが明らかになりました。そうしないとUnit、コールバック関数の戻り型が汚染される可能性があります。

ステップ2結論

私の混乱の多くは、何らかの理由で、それぞれが続きとしてshift次の写真までしかキャプチャされていないという事実に起因していると思います。shiftもちろん、そうではありません。それぞれが、次のすべてを含むブロックの残り全体shiftをキャプチャして、コールバックでの大きなネストされたコールバック状況を形成する必要があります。さらに、すべてのコールバックとすべてのCPS呼び出しメソッドは、常に(私が知る限り)returnを返す必要があります。これは、それらの結果が何もしないだけでなく、それらを呼び出す関数のreturnタイプを汚染する可能性があるためです。コールバックのチェーンの上流。resetshiftUnit

ステップ3

今、私はの署名を見ましたshift。それは私の目の前にありました:

def shift[A,B,C](fun: (A => B) => C)): A @cpsParam[B,C]

これを見て、コールバック形式の演習と組み合わせると、shiftこれを基本的に次元分析の演習に変えるのに十分な情報がここにあることに気付きました(舞台裏で何が行われているのかを完全に理解していなくても)。

method1の結果がになることを私は知っていFloatます。したがって、継続コールバック(上記のように示されて(A => B)いる)は、パラメーターとしてaを受け入れる必要がFloatあります。これはとして修正AされFloatます。したがってmethod1、現在は次のようになっています。

def method1(param:Int): Float @cpsParam[B,C] = shift { cb: (Float => B) => {
    ...
    C
  }
}

言い換えると、渡すshift関数はFloatからBに関数を取り、Cを返す必要があります。私の演習から、コールバックがUnitを返すか、物事が乱雑になることがわかります。Unitまた、コールバックの演習では、実際の結果をパラメーターとして継続に渡していたため、メソッド自体が明らかに返されるはずであることも知っています。これは、Cもユニットであることに類似しています。つまり、これはmethod1次のようになっている必要があります。

def method1(param:Int): Float @cpsParam[Unit,Unit] = shift { cb: (Float => Unit) => {
    cb(param);
  }
}

そしてmethod2、コールバックが文字列を取ることを除いて同じになります。

私が学んだこと

Unitスローされるすべての型パラメーターに混乱するのではなく、コールバック駆動型プログラムを作成している場合、結果がパラメーターとして渡されるため、関連するほとんどすべての関数が返されることを覚えておいてください。返される代わりに。

これが意味することは、私が知る限り、以外の目的はあまりないBということCです。のショートカットであるアノテーションがあるので、これは完全に理にかなっています。shiftUnit@suspendable@cps[Unit]@cpsParam[Unit,Unit]

scala-lang.orgの例がなぜそんなにくだらないのかわかりません。しかし、実際に彼らが言う必要があるのは、「それ以外のものを使用する必要がある場合MyReturnType @suspendableは、おそらくそれを間違って行っているでしょう。ちなみに、shift取る関数パラメーターもおそらく戻るはずUnitです。」そうすれば、私はまだ私の人生の最後の数日間の貴重な日を過ごすでしょう。

ハッピーエンド

上記で変更したプログラムは、完全にコンパイルされ、両方のメソッドを順番に実行します。それで、私はついにそれが正しいと信じるようになりました。あなたがCPSを深く理解している博士号を取得している場合は、私のとりとめのない誤りを訂正してください。

于 2012-02-29T17:16:01.040 に答える