Scala sequels: many shifts in the sequence

I am trying to wrap my head with complex typing problems with a continuation of scala. I read all the materials that I can find on it, including reference documents in the sequel pack. I think I understood this to some extent, and it makes sense when you think about it.

I think that my understanding of this (and some of my questions) can be best summed up by this program:

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

By the way, it is a crime that StackOverflow does not emit scala! (I stand fixed, actually it's pretty good work, but just not in the preview)

My questions are as follows:

  • Judging by the comments in the above program, what is lacking in my understanding of scala CPS? Is there ever a situation where you would not want Unit like B in @cpsParam[B,C] ?
  • The above program compiles and works (displays "5.0" ). But the problem that I am facing right now, which is causing my confusion, is that I am changing the reset block to try calling method2 after method1 :

(Apparently, you cannot have a block of code immediately after the list)

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

When I do this (which seems pretty simple), I get the following compiler error in reset (this is scala 2.10 Milestone 2):

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

I interpret this as "Your first shift returns a Float, and your second shift returns a String, and you cannot do this." That's for sure? Does this mean that you cannot use CPS to do two (or more) things in a sequence unless they have the same return type? Because it seems like a serious limitation. I assume that I either 1) There is something missing that allows you to do this, or B) There is no obvious reason why this is not possible with CPS. But which one?

I'm starting to feel different than you need to be a student after the doctrine to understand scala CPS. But of course, I’m not quite there yet.

+6
source share
1 answer

After I asked this question, I did a lot more research, and I think that now I can answer my question (I hope this is not a fake).

There are three things that I did that helped me understand the problem, and I think that anyone who has problems with scala continuations would have to follow these steps:

  • Read the original academic article on scala sequels . This is very dry, and I suspect that this is mostly crazy group ravings, but also very useful in that it gives you some insight into how the compiler converts your code based on continuation and problems with typing and cleanliness, which he is facing this.
  • Rewrite your code in a callback style. This is the most important thing you can do to really get an idea of ​​what happens to the stream of continuations and their types.
  • Examine, and I mean, really check for a signature like shift and pay attention to what it does. This will lead you to the insight that I had.

In my case, I entered the @cpsParam and cb parameter in shift everything is wrong. I am going to explain how I understood what I am doing wrong, so that anyone who is as stupid as I can follow the same steps and hopefully get some idea of ​​when the continuation compiler is silent about them.

Step 1

I have read the above article. About ten times. I still understand very little about this. But what I really understand is very useful.

Step 2

I rewrote the reset block in a callback transfer style, pretending that instead of shift , each of the methods had a second cb parameter that would use the function for the rest of the block. Here's what the reset block looks like after this:

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

See what happens? Therefore, instead of writing code that seems to block, I explicitly limit the continuation and passing them as functions to each method. Therefore, for each of these situations, it becomes clear that each of my anonymous callback functions should not return anything, and in fact they should return Unit or they will pollute the return type of the method that they pass, I think this is what the compiler tried to tell me (although I could be wrong).

Here method1 should look like for my callback style program

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

method2 similar but accepts a (String=>Unit) . Now it becomes clear that my methods should also return Unit or they can pollute the return type of callback functions.

Step 2 Conclusion

I think my confusion arose because for some reason the picture in my head was that each shift was captured only until the next shift as a continuation. Of course, this is not so; each shift must capture the rest of the reset block, including all the next shift , so that it forms a large nested callback situation in the callback. In addition, all callbacks and all methods called CPS should always (as far as I can tell) return Unit , because not only their result will never do anything, but it can infect the return type of the function that calls them, etc. d. up the callback chain.

Step 3

Now I looked at the signature shift . It was right in front of me:

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

When I looked at this, I realized that in combination with my callback style exercise, there was enough information for me (even without a complete understanding of what shift does behind the scenes) to turn this into a main exercise in size analysis.

I know that the result of method1 will be Float . Therefore, the continuation callback (indicated as (A => B) above) should accept as a parameter to Float . This commits A as a Float . Therefore, method1 as follows:

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

In other words, the function I pass to shift must take a function from Float to B and return C. Well, I know from my exercise that the callback should return Unit or things get messy. I also know that in my callback, the methods themselves should obviously return Unit , because they passed their actual result as a continuation parameter. This is similar to C, which is also a Unit. So this means that method1 should look like this:

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

And method2 will be the same, except that the callback will take a string.

What i learned

Now it seems to me that instead of getting confused in all the type parameters you selected, you can just remember that if you were writing a program with a callback, almost all the functions that were involved would return Unit , because any results passed as parameters, not returned.

This means that, as far as I can tell, for B and C in shift there will not be a big goal to be something other than Unit . This makes sense because there is an @suspendable annotation, which is a shortcut to @cps[Unit] , which is a shortcut to @cpsParam[Unit,Unit] .

I don't know why the examples at scala -lang.org are such shit. But in fact, all they had to say was "if you need to use anything other than MyReturnType @suspendable , then you are probably mistaken, and by the way, the parameter of the function that shift takes should also return Unit " . Then I would have some more precious days left in my life.

Happy ending

The program with the changes that I noticed above is fully compiled and works with both methods in sequence. So it makes me believe that I am finally right. If you are a PhD with a deep understanding of CPS, please correct any inaccuracies in my mistakes.

+4
source

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


All Articles