At its simplest, a keyless remote entry system like the one that comes with most cars these days is a pair of functions: a transmitter that broadcasts a specific code, and a receiver that responds only to that code.
Implement a Scheme procedure named (make-remote-entry-set vehicle seed) that returns a pair of procedures: (transmitter target) and (receiver code).
transmitter and receiver share a randomly-generated code, known only to them. Assume that you have a (random seed) function available.
> (define entry-set (make-remote-entry-set 'taurus 1234.0)) > (define transmitter1 (car entry-set)) > (define taurus (cdr entry-set)) > (transmitter1 taurus) (taurus accepts) > (taurus 42) (taurus rejects) > (define entry-set (make-remote-entry-set 'mustang 2618.0)) > (define transmitter2 (car entry-set)) > (define mustang (cdr entry-set)) > (transmitter1 taurus) (taurus accepts) > (transmitter2 mustang) (mustang accepts) > (transmitter1 mustang) (mustang rejects) > (transmitter2 taurus) (taurus rejects)
Remember that the values printed after calls to transmittern and receiverm are values returned by the receiver -- and not printed!
Here is a sample solution. Two notes:
Quick exercise: How could we modify our implementation to reflect this synchronization behavior?
At this point in the course, you have implemented an interpreter for Boom, a small arithmetic language. Boom has the following features:
Boom currently supports only pure functional programming. For Homework 12, you will add basic imperative features to the language: the ability to change a variable's value and the ability to sequence operations. As we learned in Session 26, these features walk hand-in-hand; having one without the other makes no sense. The addition of mutable data to the language will greatly expand the range of programs it supports -- even without procedures or an "if statement". (You will have a chance to add "if" or "while" expressions to the language as part of Homework 12, too.)
As you add mutable data to Boom, you should be sure that you understand a key distinction: between values and the variables that hold them.
Most programming languages support two kinds of value:
One of the things that distinguishes Scheme from most languages you knew before is that procedures are both denoted and expressed values.
Why does introducing variable assignment into our language require that we reconsider this topic? Couldn't we just model a variable assignment as a new addition to the environment?
A few problems would result from such an approach. On the practical side, this approach leads to environments that grow larger with each assignment statement. This makes our interpreter waste space. A loop that reassigned a variable 100 times would add 100 new bindings to the environment.
This approach also makes it difficult to determine whether a variable exists yet, and that makes it difficult for the language and interpreter to help users. Do we want to allow assignment statements to create new variables on the fly? Or should the assignment proceed only if the variable already exists?
And what if we decide to make our language statically typed? A new problem arises: how do we know if the value being assigned is legal for variables of that type?
All these problems arise because we have sidestepped the distinction between a variable's value and its location:
When we treat a variable as a value in an expression, we care only about its current value. Where the object resides in memory, or what it looks like, is immaterial. But when we make an assignment to the variable, we are concerned with both where the object resides in memory and what it looks like, because our goal is to change the value that is recorded there.
The list of questions goes on. We see this distinction in many different forms, including:
The distinction between "variable as value" and "variable as location that holds a value" is one that our interpreter should model as explicitly as possible.
The solution is to create a level of indirection between variables and their values. This intermediate level of representation corresponds to the storage aspect of the object. When we wish to treat a variable as a value, we go through this level to reach the value, When we wish to treat a variable as a location, as in an assignment, we work at the intermediate level.
For your next assignment, you will first implement an ADT that supports this kind of reasoning: the cell. Then, you will modify your interpreter so that its environment holds variable/cell pairs, rather than variable/value pairs.
With each variable associated with a cell, we will be able to refer to the value that a cell holds (when looking up the value of a variable reference) or change the value stored in the cell. Thus, cells directly support the idea of mutable data.
Note that we have now made a subtle change to the Boom language. Before introducing mutable data, the set of expressed values in our language (numbers) was identical to the set of denoted values (numbers). That is, the value of every expression was a number, and the value of every variable was a number. Now, the expressed values in our language are numbers, and the denoted values of our language are cells. Whenever we wish to determine the actual value of a variable, we "dereference" the cell, much we follow a pointer to the location of the desired value.
Rather than introduce any more new material in this unit, let's take a few minutes to consider the code of an actual Boom interpreter.
Sometimes, reading another person's version of the same code can teach you something new, or lead to an idea you can use in your own. Reader comments almost always help the program's author. So:
(As a group, we will discuss some of the strengths of the program as well as some areas for improvement.)
What part of the interpreter has been causing you the most headaches?
We have only two sessions left and a lot of cool things we might study. But what?
Is there a programming languages topic that you would like to learn, or learn more about? Send me your request by 5:00 AM Monday. Maybe I can make it happen.