0:00 / 0:00 Continuation.
Passing style is a style of programming which is typically used in languages such as JavaScript, where
after doing a particular function you specify the continuation as another function and then another
function and so on and so forth.
Well, continuation passing style in C sharp can be used in a different fashion, and I’d like to demonstrate
the way that I personally use it when describing complex algorithms.
So the situation is that you have a complicated algorithm and the question is how do you split it into
separate components?
I mean, if it’s a very long calculation, how do you split it into the different parts?
So we’re going to take a look at an example.
Let’s suppose that we have a quadratic equation solver, quadratic equation solver, like so now the
quadratic equation solver can certainly be implemented as a single function.
But internally, since we’re choosing to implement the entire algorithm as a class as opposed to just
a function, we may as well use more than one function to propagate certain data forward.
So let’s assume that we have an entry point of this quadratic equation solver and it’s called Start.
So we might have something like the following.
Public void start and this is where you would provide the argument.
So we’re trying to solve a quadratic equation, which is a X squared plus b, x plus C, we want to
find the X given the values A, B and C, so we specify the values A, B and C, double B and double
C.
Now, first of all, the first thing you do in a quadratic equation is you calculate the discriminant,
which is B squared minus four AC.
So we can say var desc equals B squared minus four, A, C, and then well, you can certainly return
the value from this location.
However, depending on whether the discriminant is positive or negative, you may end up with either
real roots or complex roots.
So we may as well do an if here and we can say if the discriminant is less than zero, then we need
a complex solution.
So we need to return, in this case something like solve complex.
With the values A, B and C, That’s the values we have which are required for solving something.
And we also have calculated the discriminant.
So now we incorporate the calculated value into the next step of our calculation.
And of course, the calculation itself is going to be a private method.
So we’re going to have a private method here called complex or solve complex rather, with the values
A, B, C, and then the discriminant as well.
So here is where we implement the actual solution.
So we can, for example, calculate the root of the discriminant using complex numbers.
So I’ll do complex dot sqrt, new complex.
We need to turn the discriminant into a complex value like this and then we return the A tuple because
there are two solutions.
So we return tuple, dot create and here we can for example, we can say either minus b plus root disk
divided by two A or the same, but with a minus.
Now, of course, we now need to talk about return types for these functions.
So the return type here has to be a tuple of two complex values.
And then of course, this means that the start has to be the tuple of two complex values as well.
Okay, so we’ve defined the overall interface.
But there is another case and the other case is where the discriminant is zero or positive, in which
case we can just solve simple return, solve simple A, B and C and the discriminant.
And here things will be somewhat easier because we’re not working with complex numbers, even though
we do have to return complex numbers.
So here is the function.
Let me rename the arguments.
So a, b.
And see here.
And of course, here we say root disk equals math dot sqrt.
So we don’t have to use the complex type.
So math.sqrt of the discriminant.
And then we return pretty much the same thing.
We return tuple dot create.
Except we now have to cast everything to a complex.
So we have to say new complex and specify the complex part as zero.
And the same goes for this line.
New complex.
Add comma zero here.
There we go.
So now what we’ve done is we’ve essentially defined an entry point for the algorithm.
That’s the only thing you can actually call.
So, I mean, if we had a class, let’s call it, well, we’re showing continuation, passing style.
Continuation passing style demo.
So the way you would do this is if you want solutions to a quadratic equation, you would say, you
would say.
Quadratic equation solver, quadratic equation solver.
Dot start and start has to be public.
Actually, we haven’t made it static, which is another thing that could be worth doing.
So at the moment I’ve just instantiated var solver equals new quadratic equation solver and then you
would say var solutions equals solver dot, and then you press and you choose start because there is
no other public API, there’s only start and that’s is where you can specify the actual arguments and
you get a complex tuple.
So you can see here that the control flow passes from start into solve complex and its solve complex
and solve simple which actually return you the values and you kind of propagate them in this chain.
And as your algorithms get bigger, you get bigger and bigger chains.
So for example, solve simple might call yet another function which returns a tuple of complex complex.
And so this kind of propagation helps you split algorithms into the separate parts.
But also another thing which I use is return flags for indicating whether the operation was a success.
Because what we’re doing with this continuation, passing style like approach is we’re specifying a
workflow and workflows.
They not only succeed, but sometimes they fail.
So let’s take a look at how this is implemented.
So let’s suppose that we are utterly unable to solve for complex numbers.
Let’s suppose that in this case it’s an error.
We don’t know how to do it.
So in this case, what we do is we make a change.
We basically say that we have an enum is going to be called workflow result and we’ll have just two
values here.
Success and failure.
And what we do in here is we basically specify the fact that we have failed.
So for this we change the return type from a tuple of complex complex, but we still need the return
value.
So what we do is we move the return value into an out parameter.
Like so.
So we now have a result and the return type is now a workflow result.
So here, because we don’t know how to handle a negative discriminant, we return workflow result failure,
but we do need to initialize the out parameter.
It cannot have a meaningful value because we don’t have an answer here.
So result is equal to null.
Now, in the case, we do a different thing.
So we we know we will succeed.
We know that we will get the right value.
But we do need to call this solve simple in order to succeed.
So we call solve simple and once again, solve simple also has to have an out parameter of pretty much
the same type.
So here in solve simple, if we go towards the end here, we need another out parameter called result.
So here we return solve simple.
We pass the arguments which are A, B, C, discriminant and result.
And of course, the result has to have the out keyword.
And let’s see what the problem here is.
So the type of solve symbol also has to be workflow result.
So let’s do that.
And then of course, instead of making a tuple here and returning it, we say result equals and we assign
the tuple and then we return workflow result success.
So in this case we succeed and we also assign the out parameter as well.
So this is how you would go about implementing the actual calculation in terms of not just the result
which you can then use.
So the, the actual construct here would be different because you do solve at the start, but this is
just a flag.
So here you have a flag indicating whether the result was true or false, and then you have here the
actual solutions, the actual solution to the equation.
So we make a local parameter here.
You obviously need the out keyword.
And then of course, you say if the flag is equal to success, this is when you can actually start using
the solution and operating on it.
So this approach, which I use quite a bit, is how you would define complex algorithms.
You can have a lot more than just an enum.
You can have, you know, fully fledged error messages and whatnot indicating why we couldn’t calculate
a particular thing.
And that is what gets propagated and gets returned through all the functions and the return value is
returned as simply a tuple.
Now you can make a tuple out of the result type as well as workflow result.
That’s also possible.
You can also build your own custom types if you want to for these kinds of purposes, but this is how
I typically do it.
So I have workflow result as the return type of all the chain of continuations and I have the actual
result as an add parameter.
The last thing I want to show is that some of the some of the things here are redundant.
Like for example, let’s take this parameter parameter C Now the parameter is actually not being used,
so you may as well remove the parameter and update all the usages.
You’ll notice it’s gone from here and it’s gone from here as well because C isn’t used in this part
of the calculation.
It’s only used for the discriminant.
It’s not used for the final part of computing the the final values.
So here you can sort of keep reducing the number of arguments and keeping only those arguments which
are actually necessary for calculating the final value.
Play Play Stop Play Play Play Play information alert