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.