The Hilbert Hotel
Summary
In 1924, the immensely gifted and influential mathematician David Hilbert wrote, in a series of unpublished lectures, about the strange and counterintuitive qualities of an infinitely capacious hotel. "Hilbert's paradox of the Grand Hotel" was eventually brought to light in 1947 in a book by George Gamow, and has since become quite famous.
In case you are not familiar with the thought experiment, it goes something like this.
Say that the Grand Hotel has infinitely many rooms, and each of these rooms is booked. For the sake of argument, let's say that each room is booked by a distinct guest; it doesn't really matter who's doing the booking, so long as every room is booked. What does matter is that the number of rooms is countably infinite, i.e., every room can be labeled with a distinct positive integer.
Say also that the Grand Hotel is at capacity, meaning that every room is currently assigned to a guest. Were it a boring old-fashioned finite hotel, it would be impossible to accommodate any new reservations without forcing guests to double up. But the infinity of the Grand Hotel offers strange and wondrous opportunities:
- When a single new guest arrives, you can accommodate him simply by asking each of your infinitely many soon-to-be-irate guests to move their belongings to the next higher numbered room. So every guest in a room n moves to room a n+1. Then the new guest moves into room 1.
- Should countably infinitely many new guests arrive all at once — possibly to visit the local theme park before the summer rush — you can accommodate them all by asking your current guests to move from room n to room 2n; this frees up all the odd numbered rooms for the newcomers.
- Similarly, but more courteously, when a countable infinity of new guests arrive, you can disturb only 1% of your current guests by having each occupant in a room divisible by 100 move to a room divisible by 200. Mind you, you are still going to upset an infinite number of guests…
Before you start calling me "Bucko" again, I'd better tell you why I'm talking about the paradox of the Grand Hotel. The real protagonists of this tutorial are tuples. A tuple is a core data structure that you would be very surprised to learn is infinite — because, in fact, it is not. But introducing the real protagonists are these apparent protagonists:
- The manager of the Grand Hotel, whose part is played by a confused human sitting in front of the Avail workbench.
- The concierge, the manager's loyal but frightened minion, whose part is played by five different entry points.
- John Doe, the conference-going gestalt being who suffuses all odd-numbered rooms at the opening of the scenario.
- Jane Doe, the conference-going gestalt being who suffuses all even-numbered rooms at the opening of the scenario.
To introduce tuples, we will stroll hand-in-hand together through the building of a booking system for the Grand Hotel. When we are finished, the result should look something like this:
Prerequisites
- Understand the material presented in the previous tutorials. You definitely need to have a good grasp of the concepts present in Guess the Number, which really starts to get into nontrivial Avail programming.
- Understand the many and varied features of
string
literals.
Goals
- Learn how to declare, initialize, and assign module variables.
- Learn how to use module variables to build cooperative entry points so that the compiler can be leveraged to handle user input.
- Learn about types and type names.
- Learn about
tuple
s andtuple
types.- Constructing a fixed-size tuple type (using
"<_`…|_>"
). - Constructing a
tuple
(using"<«_‡,»>"
and"_to_"
). - Determining the cardinality of a
tuple
(using"`|_`|"
). - Subscripting a
"tuple"
in order to obtain an element (using"_[_]"
and"_[_]else_"
). - Slicing a
tuple
(using"_[_.._]"
,"_[_..]"
, and"_[.._]"
). - Concatenating
tuple
s (using"_++_"
).
- Constructing a fixed-size tuple type (using
- Learn about message metacharacters:
- Grave accent
`
(U+0060)
(backtick). - Left-
«
(U+00AB)
and right-pointing double angle quotation marks»
(U+00BB)
(guillemets). - Double dagger
‡
(U+2021)
.
- Grave accent
- Learn more about formatting text:
- Using multiple-word format variables (using
"format_with«…!::=_‡,»"
). - Producing an English description of a
tuple
(using"“_”«else_»(as«conjunctive|disjunctive»!list with serial|Oxford|Harvard comma)"
).
- Using multiple-word format variables (using
- Learn how to determine the parity of an
"integer"
(using"_is odd"
and"_is even"
).
Complete Sources
Walkthrough
In this tutorial, we develop the infinitely accommodating Grand Hotel — or alternatively, the Hilbert Hotel — as a blessedly singular and finite source module. I can already hear your sigh of relief. "Thank goodness I don't have to read an infinitely long tutorial!" Well, my gratitude has yours beat; at least I don't have to write an infinitely long tutorial. So there.
¡Atención! This is a long tutorial! Despite my joking that I didn't write an infinitely long tutorial, I very nearly did. Sort of. So I strongly encourage you to use the workbench to play with the entry points while you are reading along. It is guaranteed to animate and facilitate the narrative. You are also advised to strike out on your own. Try modifying the tutorial code as you go. This will help you to gain an appreciation of what works and what doesn't work… and why.
Roadmap
The booking system for the Hilbert Hotel comprises five cooperating entry points that reference the same module variables and constants. The entry points are:
"What's all this`?"
(lines 100-112): Learn about the Hilbert Hotel. This method is provided for flavor only."Hey,what do you mean_`?"
(lines 114-128): With equal probability, when the module is loaded the concierge randomly decides whether to refer to the user as "sir" or "miss". This allows the user to change this form of address."Who is in room_`?"
(lines 146-153): Find out who's currently in a particular room."Assign_to room_"
(lines 155-181): Assign a guest to a room, but only if the assignment wouldn't break up the infinitude of conference goers."Evict the guest in room_"
(lines 155-181): Evict a guest, but not a conference goer.
There is only one module constant:
allTitles
(line 57): Contains all titles that can be used to address the manager.
And these are the module variables:
titleIndex
(line 58): Tracks the user's preferred form of address.inquiry
(line 59): Records the last room number that the user mentioned.guests
(line 60): Records every guest booked by"Assign_to room_"
. (But it does not record the conference goers.)
Note that a module variable is just a variable that is defined at the top level of a module, outside of any function definition. It can be referenced by any function defined subsequently in the source module.
Before diving into the code in detail, we need to introduce a couple of abstractions and our rationale for using them. In particular, we are going to introduce the abstractions of room number
and guest
so that we can use the right language for the scenario.
Type names
Let's get started by examining line 46.
Avail has an extremely rich and varied type system that is capable of expressing a wide range of concepts. While natural number
is a very good fit for room numbers in a hotel, it is not ideally named. The name "natural number" tells us only about the representation of a room number, not about its role within the program. The representation of a type describes the substrate on which its values are built and how they are constructed using entities indigenous to that substrate.
Say whaaa…? Okay, let's make that abstract a little more concrete. Take the mathematical object ten (10), for example. It has many representations. We have seen two of them in this paragraph so far. ten is an orthographic representation in the English language. 10 is an orthographic representation using Arabic numerals. 十 is an orthographic representation using Chinese numerals. Say the word for this number out loud in your native tongue, or hold up your fingers, or lay out 10 pencils in a row; these are all representations of the same concept.
But none of these representations answer the question, "10 what?" It is possible that we are simply talking about the concept of 10 itself, as in the previous paragraph, but very likely we are talking about some application of the concept. How a concept is being applied defines its role. A natural number like 10 can have many different roles depending on circumstance. It can tell you how many of something there are, e.g., this sock cap contains 10 apples. Or it can tell you which member of a collection something refers to; like, this is room 10 of the Hilbert Hotel.
Now let's tie this philosophical exposition back to the code. Line 46 introduces the method "room number"
as an alias for natural number
. Lexically subsequent to this definition, that is, after the semicolon at the end of the line 46, we can say room number
or natural number
interchangeably. But we will prefer to use room number
when it describes the role of a parameter or variable better than natural number
would.
Tuples, types, and tuple types
Line 47 is an even more important alias, because it hides a very general type constructor behind a very precise role. A type constructor is a method that computes a type from values provided as arguments. In this case the type constructor is named "<_`…|_>"
and it constructs a tuple type.
Before I can sensibly explain what a tuple type is, I should tell you what a tuple is. A tuple is a finite, ordered collection of arbitrary values called elements. Like the rooms of a hotel, each position of a tuple is uniquely identified by a natural number
, its subscript (or index). The valid subscripts of a tuple range from 1
to its cardinality, i.e., size; all other subscripts are out of bounds. Since a tuple expresses the concept of certain, specific values appearing in a predetermined order, a tuple is immutable — like the integer 10, its value can never change. You can, however, very conveniently derive new tuples from old ones via a multitude of handy library methods. This is akin to deriving new numbers from old ones using arithmetic, like adding 5 and 12 to make 17. Nothing happens to the old values as a consequence of deriving new values. They continue to exist quite happily. Nor are the "new" values really new. 17 didn't spring into existence as a consequence of summing 5 and 12. Think of the operation as having discovered 17 instead of having created it.
So far in this tutorial series, I have been using the term "type" very loosely and intuitively. I don't want to treat the subject here with full rigor — that's what the type system treatise is for — but I should at least define the term before I use it again. A type is a class of values that have similar 1) representation and 2) operational capabilities. For example, the integers 5, 10, and 81 can each 1) be described by an ordered sequence of numerals and 2) participate in arithmetic. This suggests that there is at least one type that unifies them all as instances. In fact, in Avail, every value is an instance of infinitely many types. natural number
is merely one type that includes 5, 10, and 81. "integer"
is another, as is extended integer
, as is "number"
, as is [4..82]
, and so on ad infinitum. Types come in all granularities so that they can be coarse enough or fine enough to suit any particular need.
Now let's put tuples and types together. The type tuple
encompasses every possible tuple. That's a very broad type, so broad that it's useless as a role. In building a booking system for the Hilbert Hotel, we only want to talk about a couple different subtypes of tuple
. On line 47 we introduce the role guest
as a subtype that describes every tuple such that:
- The tuple's cardinality is 2.
- The tuple's elements are
"string"
s.
The method "<_`…|_>"
constructs a tuple type from 1) a type, called the default element type, and 2) a natural number
specifying the cardinalities of its instances. The default element type specifies a type to which each element of a tuple must belong. "Why call it the default element type?", I hear you ask. "Why not just call it the element type?" An excellent question! A tuple type is permitted to specify a collection of heterogenous leading element types that constrain the types of the leading elements of its instances. The default element type then constrains any element whose subscript is not covered by the leading element types. For example, the tuple type <string, integer, double…|6>
includes every tuple such that:
- The tuple's cardinality is 6.
- The tuple's first element is a
string
. - The tuple's second element is an
integer
. - The tuple's third, fourth, fifth, and sixth elements are
double
s.
So, yay! It only took a paltry 9 paragraphs and 2 lists to explain 2 very basic lines of code! I could do this for a living!
…
…
…
Yeah. Let's move on.
Parsing repeated groups
You know how people who wear suits and stand at desks often want your last name first and your first name last? Well, the concierge of the Hilbert Hotel is just such a fellow, and Lines 49-55 were written to make people like him happy. Focus your attention on line 54. The method "<«_‡,»>"
constructs a tuple from a lexical list of values. So, here we are building a tuple whose first element is lastName
and whose second element is firstName
. This semantic ordering of two strings is what we are calling a guest
.
Several times now I've glossed over the left- «
(U+00AB)
and right-pointing double angle quotation marks »
(U+00BB)
in method names, and I'm afraid that if I do it again then you're going to hurt me. (You'll probably also hurt me if I don't tell you that these characters can simply be called guillemets.) Just remember, when your eyes are bleeding in a few paragraphs, that you made me explain this now. I didn't want to; it was your fault.
In a message, i.e., a method name, «
and »
delimit a group of message parts. If you'll pardon my recursive definition, a message part is either a token or a group of message parts. A token is a discrete unit of source text; think of tokens as words in the vocabulary of Avail. The rules for dividing a method name into tokens are rather complex, but as a rule of thumb each word (in the linguistic sense) is a separate token, and so is each punctuation character.
The guillemets do more than merely group message parts. They actually allow the enclosed group of message parts to be repeated at a call site. When the compiler parses a message send that contains guillemets, it arranges to accumulate the result of each repeated expression into a tuple that is passed to the method as a single argument. So a group of message parts delimited by «
and »
constitutes a single argument position.
A group of message parts that contain a double dagger ‡
(U+2021)
is partitioned into a left side, called the content, and a right side, called the interstice. After the first occurrence of the content, each subsequent occurrence of the content must be preceded by the interstice. That is to say, the interstice must only occur between two content expressions. This is analogous to the interstice of "Do_until_alternate with_"
, which only runs between two invocations of the body.
Now that we are properly armed, let's break down the tuple constructor, "<«_‡,»>"
. It comprises a less-than sign <
(U+003C)
, followed by a group, followed by a greater-than sign >
(U+003E)
. The content of the group is underscore _
(U+005F)
and the interstice is comma ,
(U+002C)
. The following syntax diagram illustrates how the compiler parses a send of this message:
In this diagram, element
corresponds to the underscore in "<«_‡,»>"
. Ordinarily, an underscore in a message indicates an argument position, but this underscore occurs inside the content of a group. In a context such as this, the underscore indicates a position within the repeated group at which an arbitrary expression may be parsed.
Now let's revisit line 54.
In this expression, lastName
and firstName
take turns filling in the underscore of "<«_‡,»>"
. Recall that a group behaves as a single argument position, so we expect that a function associated with the method definition accepts only a single argument. Let's verify that by looking at the actual tuple constructor as it's defined by Avail
:
Yep, it takes one argument, and that argument is a tuple. Ironically, it is not the tuple constructor itself that constructs a tuple — it is the compiler, during a parse of a send of "<«_‡,»>"
. The constructor essentially just says, "Hey, let's recognize comma-separated lists that occur inside angle brackets as tuples!"
What's all this?
The method "What's all this`?"
is a gentle introduction to the premise of the module. It's where we expect a user to begin experimenting with the Hilbert Hotel. The grave accent `
(U+0060)
, or backtick, is a message metacharacter. It is the escape character, and disables the special meaning of the following metacharacter, in this case question mark ?
(U+003F)
. I'm not going to cover the question mark metacharacter right now; just take my word that it usually has a special meaning in a method name, but not here because of the backtick. Believe me, you've learned enough message metacharacters for one lesson.
The body of this method (lines 102-111) is a send of this module's chief helper method, "Concierge says,_"
. As a quick refresher on string literals, the backslashes at the ends of lines 103-110 cause the line feeds to be excluded from the text. And the \|
at the beginning of each line causes the leading whitespace to be excluded.
If we mine the flavor text carefully, we will find two occurrences of ‘you’
. Recall from the previous tutorial that these single quotation marks are metacharacters used by "format_with«…::=_‡,»"
to indicate that a format variable must be bound to a string. But where's the send of "format_with«…::=_‡,»"
? It isn't here, so it must be inside "Concierge says,_"
. We'll study this method piecemeal throughout the tutorial.
Hey, what do you mean "miss"?
During a send of "Concierge says,_"
, the you
format variable is bound to either "sir" or "miss". But where did these titles come from, and how does the concierge choose?
Line 57 defines the module constant allTitles
, which is a tuple whose first element is "sir"
and whose second element is "miss"
. Line 58 defines the module variable titleIndex
and initializes it pseudo-randomly to either 1
or 2
. titleIndex
is used as a subscript into allTitles
, and thereby determines which title will be used to address the user. Let's sneak a peek into "Concierge says,_"
to see this in action.
On line 80, pattern
is the parameter of "Concierge says,_"
. When "What's all this`?"
sends "Concierge says,_"
, pattern
will be bound to the large block of flavor text on lines 103-111. The method "_++_"
— the concatenation operator — concatenates two tuples into a single tuple. The concatenation operator works on all tuples, and a string
is just a tuple
whose default element type is character
. In this instance, we are appending a line feed to the formatted text.
Line 81 binds the format variable you
to the result of sending "_[_]"
to allTitles
and titleIndex
. The method "_[_]"
, called the subscript operator, answers the element of its left argument at the subscript given by its right argument. So when titleIndex
is 1
it answers "sir"
, and when it's 2
it answers "miss"
.
The world is approximately evenly split between men and women, and approximately evenly split between those who identify as female and those who identify as male. So the odds are even that the concierge's initial selection of a respectful title is not exactly what you were hoping for.
The entry point "Hey,what do you mean_`?"
allows the user to object to the current appellation, prompting the concierge to switch to the other form. The predicate on line 118 checks to see if the user is objecting to the title that was actually used. If the user is objecting to being called "miss" when, in fact, he was called "sir", then the concierge simply rebuffs him (lines 120-121). If the user's objection is valid, then the concierge adjusts the form of address that will be used henceforth by updating titleIndex
(lines 125-126). In either case, the concierge's response indicates which title he intends to use for further interaction with the manager.
Who is in room 1?
The module variable inquiry
is the room number
of the current room of interest. Before calling "Concierge says,_"
, inquiry
should be bound appropriately. Its initialization to 1
is arbitrary; inquiry
must be assigned the correct room number before consulting the concierge, so we could have chosen any valid room number
here.
To find out about a particular room assignment, you can ask the concierge "Who is in room_`?"
This method is deceptively simple. It merely sets inquiry
to the specified room number
(line 150) and then bids the concierge to respond (line 151). Note that we have two new format variables here: room inquiry
and guest inquiry
. When "Concierge says,_"
is sent, the former is bound to inquiry
and the latter is bound to the appropriate guest. But how is this guest selected? Aren't there an infinite number of guests staying at the Hilbert Hotel? Somehow?
Let's peer into "Concierge says,_"
again:
Lines 83-84 bind values to the format variables room inquiry
and guest inquiry
. Notice that the format variables are provided as string literals rather than bare tokens. You are always allowed to quote a format variable in a binding expression, but only required to do so when the format variable contains non-alphanumeric characters, such as the space character here. I will explain why in a future tutorial. (But it's related to "format_with«…::=_‡,»"
expecting its binding expressions to supply their format variables as token
s.)
Line 83 simply binds the format variable room inquiry
to the current value of the module variable inquiry
. There's nothing new here.
On line 84, guest
refers to the local constant defined on line 68. We use the subscript operator to extract the first name (guest[2]
) and the last name (guest[1]
). Then we use the concatenation operator to combine the first name, a space, and the last name into a single string
. The result of the concatenation is bound to the format variable guest inquiry
.
Compile-time ambiguity
But wait! Isn't there a method named "guest"
? In fact, didn't I spend five paragraphs talking about it? How can there be a local constant that is also named guest
? How does the compiler know which one we are talking about on line 84? Does it prefer the original definition? Or perhaps it prefers the newest definition? Does it just flip a coin? Or consult a Magic 8-Ball?
I am pleased to say that the compiler uses neither arbitrary rules nor divination to resolve this dilemma. The compiler understands the expression the way that you would understand an ambiguous expression: it examines the surrounding context to see if it can infer a meaningful interpretation. Because natural language is often loose and sloppy, you would probably be content in most circumstances with discovering a likely interpretation. But the compiler refuses to proceed unless it can find an unambiguous interpretation. The last thing that you would want is for it to merrily compile errant code based on an electronic hunch. Compilation demands exactitude.
So how does the compiler disambiguate line 84? It embarks on an odyssey, in parallel, to search every syntactically permissible interpretation for exactly one that is meaningful. An interpretation is meaningful if and only if:
- It satisfies the aggregate grammar defined by the module's imports and introduced messages, i.e., the syntax of the expression has surface validity.
- It satisfies every visible grammatical restriction imposed upon the messages it sends, i.e., the interpretation is permitted by the declared rules of precedence and associativity.
- It satisfies the declared types of every argument position of the messages it sends, i.e., every argument conforms to the corresponding parameter.
- It satisfies every visible and applicable semantic restriction defined by the messages it sends, i.e., no semantic restriction invokes
"Reject parse,expected:_"
to reject it.
This sounds like rather a lot of work for the compiler to do when it encounters an expression, but in practice the ambiguity of any given call site is very low, and the parsing techniques employed by the compiler are extremely efficient and parallel. Let's walk through how the compiler parses the concatenation expression on line 84.
There are two possible interpretations of guest
: the method defined on line 47 and the local constant defined on line 68. The former is a tuple type and the latter is a tuple. There are three messages sent by this expression, but only two distinct methods are involved: "_[_]"
and "_++_"
. "_[_]"
is defined for both tuples and tuple types. For tuples, it answers the element associated with the supplied subscript; for tuple types, it answers the element type associated with the supplied subscript. Phooey. That doesn't help at all; given only this much information, both interpretations are quite reasonable. "_++_"
is likewise defined for both tuples and tuple types. For tuples, it answers the concatenation of the two tuples; for tuple types it answers a type that describes the concatenation of instances of the arguments. The interleaving string " "
comes to the rescue; its presence establishes that we are concatenating tuples, not tuple types, and permits the compiler to infer that guest
can only refer to the local constant.
Seriously, who is in room 1?
Oh, you noticed that I didn't actually tell you how to find out who's in a particular room. Can't sneak anything past you!
We saw this snippet of code earlier, but I abbreviated it to show only line 68. The method "_[_]else_"
is a "safe" version of the subscript operator. When you use the subscript operator with a valid subscript, it answers the requested element. But if the subscript is out of bounds, it becomes very cross. (*mumble mumble* raise an exception *mumble mumble*) When you use "_[_]else_"
with a valid subscript, it answers the requested element, just like the subscript operator. But if the subscript is out of bounds, it answers the value returned by the function that follows else
.
Recall that the module variable guests
contains only the guests booked by "Assign_to room_"
. So if 1 ≤ inquiry ≤ |guests|
, then it just answers guests[inquiry]
. But if inquiry > |guests|
, then we use a little Boolean logic to deduce the right answer. The method "_⊕_"
computes the exclusive disjunction, i.e., xor, of its arguments: it answers true
if exactly one of its arguments is true
. The method "_is odd"
does what it says on the tin: it says whether its integral argument is odd. So we compute the xor of the odd parities of 1) the cardinality of guests
and 2) inquiry
in order to identify the conference goer. I will let you convince yourself that the choice of John and Jane Doe is always correct.
Booking a guest
The method "Assign_to_"
accepts a guest
(called aGuest
) and a room number
(called assignedNumber
). It books aGuest
into the Hilbert Hotel. The entire method is essentially just a glorified conditional.
The predicate (line 144) compares the provided room number
against the result of the method "last available room"
.
"last available room"
gives a descriptive role to a simple calculation. The method "`|_`|"
answers the cardinality of the specified tuple
. The module variable guests
tracks every guest except for the infinitely many conference goers, and we will see shortly that the concierge refuses to break up the block of rooms assigned to the conference goers, so this calculation tells us the room number of the last room available for newcomers. We are quite willing to break up the block of newcomers, since upsetting them has no effect on our bottom line!
Now let's make sure that we have the boundary case correct. Note that guests
begins life as <>
, the empty tuple:
Before any newcomers have been assigned rooms, "last available room"
will answer 1
. This is correct; at the outset of the scenario, every room is occupied by a conference goer, so the only room that can be booked without breaking them up is room #1.
Back to "Assign_to_"
. Lines 146 and 148 compute slices of the tuple currently in guests
. A slice is a contiguous range of elements within a tuple. The method "_[_.._]"
accepts an inclusive start subscript and an inclusive end subscript. These subscripts specify the bounds of the range of elements that you'd like to obtain as a new tuple. For a given tuple t, the start index s must be a value between 1
and |t|+1
. The +1
allows an empty range to be specified at the end of the tuple, e.g., <>[1..0]
. The end index e must be a value between s - 1 and |t|
. If e = s - 1
, then the resultant tuple is <>
. The method "_[_..]"
is a convenient form of "_[_.._]"
that implicitly uses the cardinality of the target tuple as the end index.
Lines 146-148 rebind guests
to a tuple that includes aGuest
at the subscript assignedNumber
. These lines split guests
into two parts — those guests before assignedNumber
and those guests at assignedNumber
and beyond — and splice aGuest
between the parts. Why is aGuest
wrapped inside another tuple first? Isn't it already a tuple? Because we want to end up with something like:
Each element of guests
is supposed to be a guest
, and only the first result satisfies this type. Don't worry. If we had gotten it wrong, the compiler would have warned us when we tried to assign the result of the concatenation back to guests
:
(The compiler may actually produce quite a few additional suggestions regarding what went wrong, so for pedagogical reasons I have filtered that list down to the item on point.)
Now let's look at the body of the else
case. You
is a format variable that is distinct from you
. It just causes the manager's title to be capitalized, as per the binding expression on line 82:
available rooms
is another new format variable, so once again we need to peer into "Concierge says,_"
to see what's happening.
Line 78 constructs an interval using "_to_"
. An interval is just a tuple whose values happen to be consecutive integers. In this case, the resulting tuple will contain the integers 1
through last available room
, inclusive. Intervals are extremely efficient in both time and storage. No matter how large the interval, only a constant amount of time is required to compute it and only a constant amount of space is required to store it. Just be careful not to stringify an enormous interval!
Lines 85-87 bind the format variable available rooms
. The conditional on line 86 produces either "room "
or "rooms "
, depending on whether room availability is singular or plural. Line 87 uses a special stringification method, "“_”«else_»(as«conjunctive|disjunctive»!list with serial|Oxford|Harvard comma)"
, to produce a savvy English rendering of availableRooms
. The "disjunctive list" part means that the last element of the tuple will be preceded by "or"
when it is rendered to text. Finally the concatenation operator splices the result of the conditional and the textual list together.
Evicting David Hilbert
Okay, only one more entry point to cover, "Evict the guest in room_"
, and it's barely worthwhile to cover it because it's so similar to "Assign_to_"
. Let's whirl through it as quick as we can, shall we?
See? Just another glorified conditional. The predicate (line 172) checks to see if roomNumber
is a valid subscript of guests
. If so, then we fiddle with inquiry
so that the concierge will provide the correct responses (lines 174-181), and then use slicing and concatenation to ditch the unwanted guest (line 182). If the predicate evaluates to false
, then we just issue a rebuttal (lines 186-190). The rebuttal mentions the last new format variable, evictable rooms
, so we need to examine "Concierge says,_"
one last time.
Line 79 defines a local constant named evictableRooms
and binds it to an interval that covers every valid subscript of guests
. Lines 88-97 bind a value to the format variable evictable rooms
. You should be able to figure out what's going on here — even though it's a little noisy — because there is absolutely nothing new. Don't be afraid to look back at how available rooms
gets bound on lines 85-87 if you need some help.
That's all, folks!
We're done! Before moving on to the next tutorial, spend a while playing with this one. Check out the suggested exercises, and improvise some of your own. We covered many new concepts and practices, so take time to internalize them. You'll be glad that you did.
Further Exploration
This tutorial deals only with countable infinities. If you are interested in learning about uncountable infinities, then read up here.
If any of the transcripts reminded you of interactive fiction games like Colossal Cave or Infocom classics like Zork, then you might want to take a stroll down memory lane. If you are sufficiently overcome by nostalgia that you'd like to try writing such a story yourself, then you might want to check out Inform 7. (Or use Avail to start building your own domain-specific language for crafting interactive fiction!)
If the joke about having a cold sailed right over your head, or if you just want to see the "Dead Parrot Sketch" again, then watch it here (it appears to have been removed).
If you'd like to learn more about the serial comma in English punctuation, then start by reading this.
Exercises
- Show that you're not intimated by strength in numbers: modify
"Evict the guest in room_"
to permit ejection of conference goers. - Allow the conference goers to be broken up, i.e., allow a command like
Assign a guest named "Cash", "Johnny" to room 1000000
to effect a reservation rather than a flippant rebuttal. This will require a more sophisticated mechanism of tracking reservations, but it can still be done with tuples and conditionals. You are certainly welcome to use more powerful techniques, of course… - For a challenge worthy of heroes, introduce an entry point named
"Assign infinitely many of_to rooms divisible by_"
. At an external call site of this entry point, the first argument should be a send of"a guest named_,_"
. Be sure that every guest ends up in the correct room, even in the presence of multiple infinitely large parties!
‹ Guess the Number | | | Return to Learn | | | › Fibonacci |