In this session, I'm going to cover another broad category of contextual obstructions, which I call Context Passing. You've seen the type classes are about type instances of generic traits. That is they answer the question, what is the definition of TC[A] for the type class trait TC and the type argument A? If you want to make A a type parameter, we need an implicit parameter to go with it that conveys this information, what the actual definition of TC[A] is. On the other hand, they're also uses for abstracting over values of a simple type, which is not parameterized. And then the question to ask would be, what's the currently valid definition of type T? So here's an example that comes up a lot in code that uses concurrency or parallelism. To do computation in parallel, runtimes need thread schedulers. The thread scheduler is essentially a piece of software that allocates runtime on processor cores to individual parallel processing threads. There's usually a default scheduler, but it should be possible to override that choice in parts of the code because a different schedule might have better performance characteristics or better availability guarantees and so on. So the question is, how are references to schedulers propagated? In scala, they are embedded in values of types ExecutionContext. The default you see here, so there's a global execution context, which is a so called ForkJoinContext. So that example illustrates a second form of given instances. You can also define a given instance as an alias like that and as always the name is optional. So you could also write given ExecutionContext=ForkJoinContext. What happens here at the execution is that the evaluation of JoinContext is done lazily, the ForkJoinContext is created. The first time global is used either directly or as an argument to an implicit parameter. So execution contexts have this property that they rarely change. Most applications would be happy with the default execution context everywhere. But on the other hand, there should be changeable everywhere. So it should be possible at any point to override the execution context that's given by default and use a custom one. So this profile is a poster child for using implicit parameters. It means that we pass ExecutionContext as an implicit parameter to all code that needs it. And usually you'll just pick the ExecutionContext from the collar one. But at any point where you need to, you can use a different ExecutionContext just by having an explicit argument here instead of the implicitly synthesized one that the compiler gives you. So that was one example. But there are many other use cases passing a piece of context as an implicit parameter is actually quite common. For instance, we might want to propagate implicitly the current configuration or some capabilities and a set of available capabilities or a security level. Or, let's say a layout scheme to render some data or persons that have access to some data. And there's many more examples in all of these cases, the idea is that a certain information has to be propagated widely to many places in your program and it changes relatively rarely. And of course it should change functionally, you don't want to update it. So it means that this piece of information, it gets propagated down the choreograph in the trees and it's always the same except at some point, you might want to change it. And then in the sub tree there will be a change into another point, you might want to change it again and so on. But these changes would happen rarely because otherwise, if they happen often, then the right choice is an explicit parameter, it matters what the value of this parameter is. It's only, if the usual cases that it's just the same, that we want to suppress the noise, we don't want to pass it everywhere explicitly if it's everywhere the same. So as this example implies implicit parameters and context passing really are important for larger systems, small systems really need them. So let's do a slightly larger application that points to essentially something even bigger and what we want to track here is persons that have access to some data. So what we are going to do is a conference management system or to stay modest. Just one part of the conference management system. So we want to design a system to discuss papers submitted to a conference. And we assume that the papers have already been given a score by the reviewers. That's another part of the conference system that we live out here. So to discuss reviews need to see various pieces of information about the papers. But some reviewers are also all sorts of papers and an author of a paper should never see at this phase to score the paper received from the other reviewers. So the consequence of this is that every query of the conference needs to know who is seeing the results of the operations and this needs to be propagated everywhere. Because if one of the persons who sees the result of a query is the author of a paper, then that person shouldn't see the real score of the paper or be able to infer the real score of the paper. So some action has to be taken, and for that action to be taken, we need to know first who is seeing the results of an operation. For a given top level query the set of person seeing its results would largely stay the same. So I initiate a technical query and then basically it's just me who will see the results. But that's not always the case, there are scenarios where we imagine this could change. For instance, I could delegate part of a task to another person and if I do that, then it's myself and the other person who has access to the scores of that paper. So if either of us is the author, then this courses should be suppressed. So here's the outline of that conference management system. So we model persons with a case class and a name, a paper is a case class that consists of a title, the authors which are a list of persons and a body. The conference management system itself provides a class conference that essentially already gets the ratings. The ratings are a repeated argument from which consists of pairs paper and into the score. So I can immediately convert that to a map with just two map of the sequence. And that would give me the real score of a paper so that the actual the score that comes from the actual ratings. Then another thing I can compute immediately is the list of papers that have been submitted to the conference because that's just the first part of the ratings and converted to a list. The next function we look at this score which gives us the score of a paper and that's not always the real score. Because if there is an author of the paper that is also a viewer of the result, then we should mask the real score. So what we do, instead of giving you back the real score is just give back a very low made up value -100. And if that's not the case, so the authors of the paper are disjoined from the viewers, then they give you back the real score. So what this implies is that the scoring function must know who finally will see the result of this. But it's not just score that needs to see the viewers, it's also functions that you score, such as this one. So the rankings functions, ranks the list of papers according to scores of papers with the higher score would come first and, Papers with the lowest score would come last. So here's the definition of ranking. So it's papers sought by score in rivers and of course score needs to see viewers and therefore rankings also needs to know who the viewers are. The next function here is called ask. So that's essentially a top level query. So we have the person who asks a query and the query is a function from viewers to T so the person submits a query. The query takes the viewers argument and gives you back the result that the person is interested in, which is the type parameters or can be arbitrary. And the implementation is simply we call the query with the person as the viewer. So it's a set of people. And finally we have to delegate to method which solves the task of delegating part of the computation in a query to some other person. So query will be run with the current viewers, which has to be passed into delegate to and in addition this other person, both the current viewers and person can now see the scores of the paper. So here we have an example data Set. So we have five persons who happen to be both reviewers and authors. And we have four submitted papers that you see here. So here's the titles here, the authors and the body I leave out in each of the cases and they all, each paper has a score that you see here on the right. Now, here's an example query. Query is which authors have at least two papers with the score over 80. So we define that query in a method highly ranked, prolific authors and as we will see, the result of that query will depend on who's asking. So we also passed the person that's asking the query as a parameter and we get back a set of persons which are the highly ranked prolific authors. So what we do is we ask the conference system with the current person so that's asking. And this query here that you see here locally so that query as every query has to take a viewer's parameters or a set of persons parameter and it gives you back a set of persons. So what it does is it first computes the highly ranked papers, highly ranked papers means we get the rankings and we take the top papers. So we have to take while here top papers means that the score of a paper would be greater than 80. So we take them from the rankings list until the ranking is lower than 80 or equal to 80. And these top papers are converted to a set here. So once we have highly ranked papers we can complete the query with this four expression that you see here. We let p1 range overall highly ranked papers. P2 the same we let author range over the authors of paper p1. If p1 and p2 are two different papers and the author list of paper p2 also contains our author. Then the author is a highly ranked prolific author and we return it. So now you see the rankings function is parameter arized with the current viewers. So is the score function. So the answer of these functions will depend on who is the current viewer and that in turn depends on who's asking. So I've put the conference management in the work shoot, you see it here. We have the query here and now we have a test as method which says, well we call highly ranked prolific authors with the given person as the person who is asking and we just essentially printed out slightly prettified taking the name and separating with commas. And if we now to run this test as different persons who are asking, so blacksmith able or red, then you get different answers. Because let's say in the first case where it's Mrs Black that asks you see here, Mrs Black is a co-author with Mr Smith and that means that she won't see this paper on the score of this paper will be minus 100. So that means that the other paper of Mr Smith, who is a highly prolific author is seen, but this one is not. So from the viewpoint of Mrs Black, Mr smith has only one highly ranked Paper, the 1st one. If we ask as Mr Smith then we'll find that Mr Smith has these two papers, so you can't see their scores. And in the other papers the the author doesn't figure so from the viewpoint of Mr Smith that he doesn't see himself as a highly ranked prolific author. And if you finally look at Mr Ed, so Mr Ed he's here he hasn't published with the other, so he has a complete picture of these first three papers. So for him, both Smith and Mrs Peters are highly ranked politic authors. They both have two papers in the in the list of these three. Okay, nice. But there is a problem so far passing the viewers, that is a convention that means nothing prevents just passing the empty set of viewers to a query. So our query highly ranked prolific authors could also have looked like that. It would be type correct. We would say, well we want the rankings and nobody seeing that really, and and then we take the top ranked papers according to the score, which again, nobody has seen. That's a lie, of course. But the type system will not catch us out with that. So we can fix that by making the viewers type alias opaque. Previously we've seen that viewers was just an alias for set of persons and that's something that essentially is for everyone to see. So we can everywhere, freely exchanged viewers with set of persons. The left hand side and the right hand side are the same. But if we write the type alias with an opaque modifier that you see here, then it becomes an opaque type alias and the rules change. In fact, given an opaque areas such as this one here, the equality that viewers and set of persons is the same. Is known only within the scope where the aliases defined in this case, within the conference management object. Everywhere else, viewers is treated as a separate abstract type. So it's just a new type about which we know nothing. We certainly do not know it's an alias of set of person. So if we do that, how does it help against tampering? Well, when asking a query, we have to pass a viewer's set to the conference management methods like rankings or score. But viewers is an unknown abstract type now. So, but because the queries are formulated outside the conference objects, viewers for them is an unknown abstract type. Hence there's no way to create a viewer's value outside the conference management object. So the only way to get a viewer's value is in the parameter of a query where the conference management system provides the actual value. So that means in this query here, we are forced to pass viewers here and here because that's the only viewers value we have access to. Indeed, if you look here, so if you can't create a viewer's value, then the only viewers value we see is that one here in the parameter of the query. So we're forced to pass this on to rankings and score. There's a caveat that actually works only if queries are not nested. If queries would be nested, then we would have a choice, we could pick an outer parameter of outer query or a parameter of an inner query, so that would still allow cheating. So for the moment, let's assume that queries are not nested, that every query we formulate is a top level query. So let's look back to the conference management code, one downside is that we have to pass viewers arguments along everywhere they are needed. And that's tedious. And it also seems pointless since by design there's only a single value we could pass. So the question is, can't we automate that? And the answer is of course, just use implicit parameters. So let's apply this change to direct sheet. We make every viewers parameter a using clause. And at the same time, we avoid explicit arguments with viewers because they can be passed implicitly now. So we still have a problem here. It says that there's no implicit argument of the viewers type available. And that's because here we actually haven't made viewers using clause because that was essentially the function type prevented us from doing that. So we need to correct that by essentially making it a given instance that's possible implicitly. So what we can write is given viewers equals viewers, Equal viewers and that's it. So now things are good. Okay, so what we've done is we have changed every explicit parameter viewers to a using clause and we have omitted the actual arguments that refer to these viewers. So we've in a natural gun from this to this. So the parameters get the using keywords and the applications get the viewers arguments dropped. So here we have viewers in score and now it's gone, and here's here's the same thing. So we pass viewers twice, but afterwards we don't need to do that anymore. So you might say adding all these using classes just to save three explicit arguments is not really worth it, is it? And I'd have to agree with you. But remember, this is sort of a extremely minimized natural system. Real systems are much much larger and you would have many, many more implicit parameters where the savings would actually add up. So data about this have been collected in a paper called Scala Implicit are Everywhere. And that appeared at the OOPSLA Conference in 2019. It analyzed a large body of code consisting of 7280 Scala projects put together 18 million lines of code. And it found that 98 % of projects use Implicits. 78% of projects define Implicit and 27% of call sites use Implicits. So it means since 27 of cold sides, if they all had needed explicit parameters, then that would lead to a significant blow up in code size for these projects. Implicits really do save a lot of tedium and repetition in these real world called Basis. So in our case, the implicit parameters are of type viewers, which is an opaque type alias. And that has another benefit. Since outside conference management viewers is a type different from all others, there's no chance to connect the viewers implicit parameter with given instances for other types. On the other hand, if viewers was a regular type alias of set of persons, we might accidentally have given instances for other set of persons in scope, and then we would get cross talk. We would then we get sets that are not intended as eligible candidates for viewers, parameters that are eligible nevertheless. So that's a problem. So the moral of that is that given instances that you define should have specific types. That means types that are don't appear very often in the rest of the program outside this use case of passing implicits or there should be local in scope or there should be both. So for instance, this would be a really terrible idea that you write a global given Int =1. So that means we have a default integer, that's 1. And then use that in some functions like this f function that uses a delta and returns x + delta. That's really bad because you can do that once in your program. But if somebody else does the same thing, then you would get spurious ambiguous instances and generally a huge mess. So never use a common types, such as Int or string as a type of a globally visible given instance. The best thing is you define your own class that essentially lives mostly for being passed as an implicit or you define an opaque type. So here's an exercise, you've seen in week 4 of the first course an enum for arithmetic expressions. It consisted of numbers, sums, products and variable cases, let's augment it with a Let Form. So now an expression is a number or a sum of two expressions or a product of two expressions or a variable with the name or a let binding that says the variable with this name is defined to be this right hand side in the scope, which is given by this body here. The task is to write an evaluation function for expressions of this type, def eval takes an expression returns an end and you should fill in the triple question marks here. So this let note let x, e1, e2, should be evaluated as you would evaluate in Scala the block val x= e1 and then e2. And you can furthermore assume that every variable x occurs in the body b of an enclosing let(x, e, b). So that means it won't have variables that don't have enclosing definition. I have a hint for a solution on the next slide. If you want the hint and keep viewing. If not you might stop the video here. So here's the solution hint. One issue we have is that given val node like val("abc"), we need to know the value of abc. So we'd have to look in an enclosing let finding that defines abc. So how do we figure that out? Well, the idea is to use a map from variable names to their defined values and to use that map as an implicit parameter. Initially the map is empty, no variables are defined and that every let node like here, we augment the map with the binding that is here, the right hand side R. So this suggests the fallen code outlined in our evaluation function. We have a local function called the recur because it does the actual recursion going through the expression tree. It takes the actual expression to evaluate and it also takes this environment, which is a map from strings to end and it returns an end. And then we start the computation off by calling recur with the original expression and the empty map. So let's develop the solution step by step. We have our original outline, we have the recur function and it's pretty obvious that we would proceed by a pattern match on what the expression is. So we have five cases and we have to fill in all five of them. If the expression is a number then its value is just the number that's stored in the node. If the expression is a sum then the value is recur x + recur y. Both recurrences of course need an environment but that environment is past implicitly, you don't need to write it here. In the same vein product x y, the value is recur x x recur y. Now what's the value of a variable? Well, we need to look it up in the environment. We say the value of a variable is whatever is the entry in and under that name. And since we assume the global condition that every name is declared, we know that every name will be in the map and we can use a map apply here safely. Otherwise we would have to use an environment.getname and handle the arrow case appropriately. Finally, for a let node like this, the result is a recur of the body part using an augmented environment. So the new environment is the old one and the additional binding that the name here, has now the value that's given by recur of the right hand side. So we compute the right hand side with recur, combine it with the name added to the environment and compute the body with that augmented environment. So again, you might ask, well, was it worth using an implicit argument for the environment? And I guess in this small example, not. But again, this example is just essentially an illustrator for something much larger. A real compiler interpreter would have maybe not five cases but 50. And it would not call itself or something else that needs the environment twice but dozens of times. So then of course these things multiply and you get numbers where it's really worthwhile not to have to pass the environment manually to every little nook of your computation graph.