X-Git-Url: http://lambda.jimpryor.net/git/gitweb.cgi?p=lambda.git;a=blobdiff_plain;f=topics%2Fweek3_unit.mdwn;h=548a58d514288dd70834e85b4bd94c4b02c62935;hp=54f0a83f8b4d99f70700c877e74025a36e5d0d83;hb=a4d2693effe839524592f4427465ff8d97625302;hpb=1c616d1aeb687348c467d78b8149d5b415be2328
diff --git a/topics/week3_unit.mdwn b/topics/week3_unit.mdwn
index 54f0a83f..548a58d5 100644
--- a/topics/week3_unit.mdwn
+++ b/topics/week3_unit.mdwn
@@ -150,7 +150,7 @@ and expect it to work the same as `(f 5 0 #f)`. Chicken will just use the first
Instead, Scheme generally works with heavier-weight collections. But which ones? Let's focus on triples first (or really any *n*-tuple, for *n* at least two), where the answer is already complex. It will become even more so in the case of unit(s).
-Scheme has two parallel notions for expressing longer *n*-tuples. The more straightforward one is called a *vector*. You can build a vector in Scheme like this:
+Scheme has two parallel notions for expressing longer *n*-tuples. The more straightforward one is called a **vector**. You can build a vector in Scheme like this:
(vector 5 0 #f)
@@ -160,11 +160,12 @@ and Scheme will display the result like this:
--- perhaps with a further single-quote prefix, depending on your configuration. It's also possible to specify a vector using that latter syntax. Vectors are like tuples in Kapulet, Haskell, and OCaml, in that their different elements can be of heterogenous types. They are like Kapulet's heavyweight tuples in that they can be assigned to single variables, can be discrete elements in other structures, including other vectors, and so on. Scheme vectors are *unlike* the tuple structures in the other languages in that they are usually *mutable*: one and numerically the same vector container can contain different elements at different stages in the program's evaluation. But some Scheme implementations also have immutable vectors.
-The other notion in Scheme for expressing longer *n*-tuples is what I'll call the possibly-improper list, or *imp*. (This isn't standard Scheme terminology. I think it's conceptually cleaner to start here and work your way torward the standard Scheme ways of talking.) I won't say yet how you tell Scheme to construct an imp, but they are displayed like this:
+
+The other notion in Scheme for expressing longer *n*-tuples is what I'll call the possibly-improper list, or **imp**. (This isn't standard Scheme terminology. I think it's conceptually cleaner to start here and work your way toward the standard Scheme ways of talking.) I won't say yet how you tell Scheme to construct an imp, but they are displayed like this:
(5 0 . #f)
---- or perhaps with a further single-quote prefix, depending on your configuration. In the special case where the imp is of length 2, these are called "dotted pairs":
+--- or perhaps with a further single-quote prefix, depending on your configuration. In the special case where the imp is of length two, these are called **dotted pairs**:
(0 . #f)
@@ -181,7 +182,7 @@ you won't get the same thing. Instead, you'll get a length-three imp whose first
'(5 (- 3 y) . #f)
-you won't get an imp whose second element is `0`, even when the variable `y` is bound to the value `3`. Instead, you'll get an imp whose second element is *another imp*, that begins with the symbol `'-` and continues with the number 3.
+you won't get an imp whose second element is `0`, even when the variable `y` is bound to the value `3`. Instead, you'll get an imp whose second element is *another imp*, that begins with the symbol `'-` and continues with the number `3`.
(By the way, in all of these languages the initial position in a sequence is called position 0, the next position 1, and so on. Some languages start counting from 0, others start counting from 1. Nowadays, most do the former. When we use English ordinals in these web pages, though, we will always use "first" to mean the initial position.)
@@ -205,6 +206,8 @@ But if you submit *that, unquoted* expression to Scheme, it gets *evaluated* to
that refers to *the imp itself*, rather than to the result of evaluating it in the way just described. Now you see why we call this "quotation", and use the symbol we do for it. You might also see the relation between the *symbol* `'y` (or just `y` when it occurs embedded in the quoted imp above, or even more deeply embedded, as in `'(5 (- 3 y) . #f)`) and the *variable* `y`. For Scheme, symbols just *are* variables, only not yet evaluated. That's why we write the symbol with an initial single-quote.
+
+
The major difference in Scheme between vectors and imps is that imps have this special relation to Scheme's own syntax. Scheme treats its code as itself being complex imps, not as being complex vectors. Another difference is that under the hood, the computer implements vectors and imps differently. Vectors store all of their elements in a contiguous block of memory (an "array"), whereas imps may store them scattered all over the place (as a "linked list"). On early computing hardware, the latter was often-times more useful; on contemporary hardware, it's much less so. But neither vectors nor imps are closer models of (heavyweight) tuples in Kapulet, OCaml, and Haskell than the other. They can both contain type-heterogenous elements, including other vectors and/or imps. Like vectors, but unlike the structures in other languages, imps in Scheme are by default mutable. But many implementations also offer immutable imps.
Okay, that's the story you should first get your head around about longer Scheme containers. We'll talk about shorter containers, including unit(s), in a moment. But I've used idiosyncratic language in talking about these "imp"s, and I've suppressed some complications, which we should now consider.
@@ -288,7 +291,7 @@ Using *unit* as a parameter in a data structure
As I mentioned in class, you might sometimes want to use *unit* as a parameter to some existing data structure. For instance, sequences or lists are data structures that have one variant `[]` with no parameters, and a second variant with both a head parameter and a tail parameter that has to be another list. What is the head parameter? Well, in some cases it might be a Number, or a Boolean, or another atomic symbol. Or even a function. But in some cases we might not care about the specific identity of the head. We might only care about the list structure. (For lists, all there is to their structure is their *length*. For other structures, their structure might be more complex and encode more information.) We *could* just make a list of Numbers, and ignore the identity of the particular Numbers used. But it's ugly to make arbitrary choices about which Numbers to use, when we really don't care. It can also mislead readers of our code about how the code works. If we want to specify clearly that we don't care about the identity of the head element, we could instead make it a list of *Units*, where there is only ever one choice for which instance of that type to use.
-If the notion so expressed is important enough, we might give it its *own*, dedicated data structure, that just left out the head parameter. Informationally, it's all the same whether you omit some parameter or include it but offer only one choice for what it can be. Indeed I suggested that this is a helpful way to think of Numbers as compared to Lists.
+If the notion so expressed is important enough, we might give it its *own*, dedicated data structure, that just left out the head parameter. Informationally, it's all the same whether you omit some parameter or include it but offer only one choice for what it can be. Indeed I suggested that this is a helpful way to think of Numbers as compared to Lists. (In Chapter 6 of *The Little Schemer*, they also briefly explore representing the number `4` as the list `'(() () () ())`.)
But sometimes the more general data structure you're working with will be well-developed, and have lots of code already built up around it, prepared to handle parameters of many types. And the special case where you don't care about the identity of the parameter might be more limited purpose, that's easier to just piggy-back on the more general notion than to write separate code for. In these cases *Units* can be a useful choice for the type of some parameter, precisely because they are uninformative.
@@ -298,7 +301,7 @@ But sometimes the more general data structure you're working with will be well-d
Returning *unit* as a result
============================
-Some functions return *Units* as their results. How could that ever be useful? Since there's only ever one result, it seems like the function would have to be a constant function, from whatever arguments it was willing to accept, to that result. Now sometimes constant functions might be useful; for example, you might have a function that expected some predicate to apply to numbers, and in a particular case you might want every number to count as passing, so you could supply the constant function from numbers to truth. But here the constant function's usefulness depends on the possibility of your choosing it rather than some other function with the same result type. Even if you limited yourself only to *constant* functions to boolean results, you still have two to choose from, and that choice can encode some information. When we turn to constant functions from some arguments into the *Unit* type, on the other hand, there is only one result that any such function could constantly return. What would be the point?
+Some functions return *Units* as their results. How could that ever be useful? Since there's only ever one result, it seems like the function would have to be a constant function, from whatever arguments it was willing to accept, to that result. Now sometimes constant functions might be useful; for example, you might have an operation that expected some predicate to apply to numbers, and in a particular case you might want every number to count as passing, so you could supply the constant predicate from numbers to truth. But here the constant function's usefulness depends on the possibility of your choosing it rather than some other function with the same result type. Even if you limited yourself only to *constant* functions to Boolean results, you still have two to choose from, and that choice can encode some information. When we turn to constant functions from some arguments into the *Unit* type, on the other hand, there is only one result that any such function could constantly return. What would be the point?
The point emerges when we have functions that do more than merely *evaluate* other functions and relations on their arguments. Some functions additionally perform *side*-effects, like vocalizing (or printing) their arguments, or changing what the first element in some mutable list value is, or so on. A function can perform side-effects and *also* return a useful value. For example, we might have a "noisy-successor" function that vocalizes the value of its number argument, and evaluates to that argument's successor. So if I ask the computer to evaluate: