Good, so now for the abstract signal, here's one piece of information. That's the currentValue. 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 subclasses 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 color to the observers set, and we would return the currentValue. So let's look next when and how currentValue is evaluated. So a signal value is evaluated using the computeValue() 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 reevaluate the signal. And that also happens with computeValue(). So here is the implementation of computeValue(). What we do is we call eval. That gives us a newValue. 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, a change was observed if there are observers. So the set of observers is notEmpty, and the newValue is different from the currentValue. In that case, we have to notify the observers that they should also do a computeValue() in turn. After we determined that, we set the currentValue to newValue. And if a change was observed, then we go through the observers. And for each observer, we call its computeValue() method in turn. But before we do that, we reset the observers to the empty set, because as I have explained before, the computeValue() of these dependent signals will actually reinsert them in our set. So that's the internal workings of the abstract signal class. How are signals created? Well, there are two ways. We can create a regular signal, or a signal wire. So to create a regular signal, we have to supply factory method that creates a signal given a call by name expression. And it simply creates an abstract signal and says well, it's eval method is essentially whenever it's called the evaluation of this by name, parameter of this expression. And as an initialization step at this point, it will compute the values or currentValue is to find the other kind of signal is a variable signal that also takes an initial expression, and assigns eval to be that expression. But it also has an update method that allows to change eval to be a new expression. After each initialization or change, the currentValue T is computed. So we model signal wires with a real wire 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 here. We have the companion object, the observer type, the caller, which is [INAUDIBLE], 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. 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 well, 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() becomes s(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 idea is how about we make the signal evaluation expressions implicit function types? So instead of being a call by name parameter arrow T, an expression would be an implicit function type that gets an observer implicitly and returns a T. That means all caller parameters are passed implicitly. In the following, we'll use a type alias for this. So we'll write Observed[T] for observer question mark arrow T. So let's see what this would look like. So in the trait signal, my apply method would now return an Observed[T]. So implicit function that takes the observer who wants to know the value of that signal, and returns the T. Likewise, when I create a signal, I don't pass a by-name parameter expression. I pass an Observed[T]. Again, it's an expression that takes an observer who wants to know the value of that expression, and returns a T. And they do that everywhere also for variable initialization, and for the update. Let's switch over to the worksheet to do that. So I define my type alias, Observed[T] is observer implicit function type T. I define my apply method for signals to be to return an Observed[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[T]. So eval now also takes an implicit observer and returns a T. And that means that when I compute a newValue 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 apply method in abstract signal also returns an Observed[T], as we've seen. And the creation methods all taken Observed[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 this situation, we define a special given instance called noObserver in the signal object. So here it is. So it's a given instance, noObserver. It's an abstract signal of nothing. Its eval method is undefined. It will never get evaluated. And it's computeValue() method is simply units, so computeValue() does nothing. So that means that if a signal changes, and the caller is noObserver, unchanged, the signal will call computeValue(). But that's a. Since noObserver 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 cyclic signal definition that we saw earlier comes from this line here. So that prevents calls like s() = s() + 1 for 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 FRP 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 I talked about paradigms. Whereas, 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 wearing function. So that gives us an interesting technique to put a functional view on top of a system that changes state over time.