In this session, we lift the lid and look at a possible implementation of this reactive programming framework. It will turn out that the implementation is actually surprisingly simple. So concretely we develop a simple implementation of signals and signal Vars, which together make up the basis of our approach to functional reactive programming. The classes are all assumed to be in a package frp. The user-facing API's are summarized in the next slides. So we have a trade signal of type T and the signal has an apply method that gives you the value of that signal at the current point in time. There's also a factory method, a creation method in the object signal. So to define a signal, you have to pass it an expression which will be evaluated by name here. That essentially defines what that signal is supposed to be at any point in time. There is a subclass Var of signals. So that signal Var, it against takes an expression that defines the initial meaning of the Var, but it also has an update method that allows to change that meaning at future points in time. Note, that signals are covariant but Vars are not, not that the type parameter T appears only in result position here. So it can be covariant, whereas for variables we see that the type T appears in result position because variables inherit from signal but also in parameter position and that's a negative occurrence. That's why the type variable here must be in variant, and that matches. Of course the intuition that beautiful things are always in variant. So that was the interface. Let's look now at the possible implementation. So here's the basic idea. Each signal maintains three pieces of information as its state. First is its current value. Second is the expression that defines the current value for signal Vars. That expression might change for other signals. It's constant. And finally it maintains a set of observers. That's the other signals that depend on the signal's value, then if the signal changes, all observers of the signal will have to be re-evaluated. So let's look at this in detail. How do we record dependencies in this observer variable? Well, when evaluating a signal value of the expression, we need to know which signal, let's call it caller, gets defined or updated by the expression. Essentially, whenever we in demand the current value of a signal, we have to say who demands that, which other signal needs to know, if we know the caller and executing a signal. So, calling the apply method of a signal, means adding caller to the observers of the signal. So at two point somebody demands the value of a signal that somebody gets added to the observers of that signal. So then later, when the signal's value changes or previously observing signals are re evaluated and the set, sick observers is cleared. So why do we clear the set? Well, it's because when we re-evaluate all the observing signals, then if they still need to know the value of the same signal, then they will enter themselves again into the set by executing one of these operations. So this clearing and re-entering on evaluation means that we can have dynamically changing signal graphs. At one time. For instance, we could have a signal S1 that depends on, let's say S2 and S3. So let's say it's S2 plus S3. So that means if either one of these signals changes will need to re-evaluate S1. But it could mean that, depending on how as one is defined on re-evaluation, it will decide then to re-evaluate to a different expression, let's say it's a signal variant can do that. So it could be that after that, as one really wants to know S3 times S4. So it has changed its formula. So if that happens then the signal S2 and S3 are both cleared in S1, so S1 is no longer in their signal sets. And then on re-evaluation S3 will re-enter S1 because S1 wants to know the value of S3 and furthermore, S4 will re-enter S1 as well. But S2 won't enter S1 anymore. So that means that set of dependencies changes dynamically with the updated signal graph here. Good, for the moment let's assume that this caller, that is the calling signal is provided magically. We'll discuss later how to make that happen. So the signal trait is implemented by a class abstract signal in the signal object. That's a useful and common implementation technique because it allows us to hide global implementation details in the enclosing object. So the trade is outside but it's a pure trade. Just the interface and the implementation is inside the signal object. The implementation is called abstract signal and the extend signal. So we would have object signal, so we would have object signal around here. So inside that signal object we provide the caller, which is magic for now. So the caller is an observer and an observer is just an abstract signals on implementation of signal where it actually doesn't matter what's the type of that signal. So you can write a question mark here to leave that type open. And as it's good practice, the observer type aliases defined opaque. So from the outside of signal, we don't know what observer is defined as and that's how it should be. Good. So now for the abstract signal, here's one piece of information that's the current value. It's undefined initially. The set of observers is the empty set initially. And then we have the eval method which is a function that takes no parameters and returns a T. And that will be defined in sub classes of abstract signal. So if we want to get the value of an abstract signal with the apply method. What we would do is we would add the caller to the observers set and we would return the current value. So let's look next when and how current value is evaluated. So a signal value is evaluated using the compute value method. And that happens at two points. First, when the signal is initialized, we have to compute the value and second, if any of the observed signals, so the dependent signals changes its value. We have to re-evaluate the signal. And that also happens with compute value. So here is the implementation of compute value. What we do is we call eval, that gives us a new value and now we have to decide whether we should propagate a change to any signals that depend on our signal. So the way we do that is we say, well, change was observed if there are observers, so the set of observers is not empty and the new value is different from the current value. In that case we have to notify the observers that they should also do a compute value in turn, after we determined that we set the current value to new value. And if a change was observed, then we go through the observers and for each observer, we call it's compute value method in turn. But before we do that, we reset the observers to the empty set because, as I have explained before, the compute value of these dependent signals will actually re-insert them in our set. So that's the internal working of the abstract signal class. How are signals created? Well, there are two ways we can create a regular signal or a signal Var. So to create a regular signal, we have to supply factory method that creates a signal given a call by name expression. It simply creates an abstract signal that says, well, it's eval method is essentially whenever it's called T evaluation of this by name parameter of this expression. And as an initialization step at this point, it will compute the values of current value is to find. The other kind of signal is a variable signal that also takes an initial expression and assigns evolved to be that expression. But it also has an update method that allows to change evolve to be a new expression. After each initialization or change the current value T Is computed. So we model signal Vars with a real Var inside the signal where for normal signal eval was allowed, constant for a signal Var it is a variable. So we can look at everything together in this worksheet, we have the signal trade here, we have the companion object. The observer type, the caller, which is so far magic. The implementation of the abstract signal. The creation method of the abstract signal and the subclass signal Vars that allow us to update a signal during computation. Yeah. So the missing bit here is how is caller implemented. So how do we know who's calling? How do we find out on whose behalf a signal expression is evaluated. The most robust way of solving this is to pass the caller along to every expression that is evaluated. So instead of having a by name parameter expression. By name some type, we'd have a function that says while the expression it gets the actual observer who wants to know the value and it returns the type. Then the signal can store this observer in its internal observers structure. So that means when evaluating a signal S [INAUDIBLE] becomes S of caller because we have to pass the caller along. The problem is that this causes lots of boilerplate code and it's very easy to get wrong. So one interesting ideas. How about we make the signal evaluation expressions implicit function types. So instead of being a call by name, parameter arrow T. And expression would be an implicit function type that gets an observer implicitly and returns at T. That means all caller parameters are passed implicitly. In the following will use the type alias for this. So we'll write observed of T for observer, question mark, arrow T. So let's see what this would look like. So in the trade signal, my apply method would now return an observed of T. So implicit function the text observer who wants to know the value of that signal and returns to T. Likewise, when I create a signal, I don't pass a by name parameter expression, I pass an observed of T. Again, it's an expression that takes an observer who wants to know the value of that expression and returns a T. That do that everywhere, also for variable initialization. And for the update. That switch over to the worksheet to do that. So, I define my type alias, observed of T is observer implicit function type T. I define my apply method for signals to be to return and observed of T. Now I can redefine my caller method, so I can say well if an observer is always implicitly known then my caller can just use that and return it. So now I know who's calling. Now my eval method is no longer a function from no parameters to T. It's an observed of T. So eval now also takes an implicit observer and returns a T. And that means that when I compute a new value of a signal, I have to pass an observer to the eval method. And in this case the observer is the signal itself. So the eval method gets evaluated on behalf of the current signal. So the current signal will show up as a dependency in the observer set of any signals that get computed by eval. Now let's go on. The applying method in abstract signal also returns an observed of T as we've seen. And the creation methods all taken observed of T. Which means that now when we defining value can be just this expression. And that's it. Now, what we see here is, I have paired this new implementation with the bank account that we had before and we get lots of errors. So of course they all say no implicit argument of type observer was found. So what happens here is that we are in a top level application and essentially we want to call signals that we have defined from outside the signal and of course so far there's no observer there. So what we observe is that at the root it's the application that evaluates a signal. So there's no other signal. That's the caller. To deal with the situation, we define a special given instance called no observer in the signal object. So here it is. So it's a given instance. No observer. It's an abstract signal of nothing. It's eval method is undefined. It will never get evaluated and it's compute value method is simply unit. So compute value, does nothing. So that means that if a signal changes and the caller is no observer on change, the signal will call compute value. But that's a no up. Since no observer is a given instance in the companion object of signal, it's always applicable when an observer is needed. But inside signal expressions, it's the implicitly provided observer that takes precedence since the implicitly provided observer is in the lexical enclosing scope. So in the triple dots here, you will see the implicitly provided observer which is essentially one that got passed through this signal expression. So that's how it all works out. So we do this change to the worksheet and then the errors go away and our user interaction is what we've seen before. So that's the complete implementation that we needed to make that work. So one more remark, the psychic signal definition that you saw earlier comes from this line here. So that prevents calls like S equals S plus 1 or 4 signals S, which, as we've seen, make no sense. So to summarize, you've been given a quick tour of functional reactive programming with some usage examples and an implementation. This is really just a taster. There's much more to be discovered in particular. We only covered one particular style of FRP, discrete signals changed by events. Some other variants of FRP also treat continuous signals, values in these systems are often computed by sampling instead of event propagation. So, FLP has several variants, but they all have one thing in common, namely that we abstract from individual events and changes to time varying signals that's a very functional approach of doing things. It's quite reminiscent of the first lecture of this course where talked about paradigms, where's that functional programming is the transformation of complex data and here, instead of individual events and state changes, we have the same thing. We treat a signal as a complex time bearing function, so that gives us an interesting technique to put a functional view on top of a system that changes state over time.