Earlier we had our first look at types. Until now, we have only seen the types that Pyret provides us, which is an interesting but nevertheless quite limited set. Most programs we write will contain many more kinds of data.
An iTunes entry contains a bunch of information about a single song: not only its name but also its singer, its length, its genre, and so on.
Your GMail application contains a bunch of information about a single message: its sender, the subject line, the conversation it’s part of, the body, and quite a bit more.
A traffic light can be in different states: red, yellow, or green.Yes, in some countries there are different or more colors and color-combinations. Collectively, they represent one thing: a new type called a traffic light state.
A zoo consists of many kinds of animals. Collectively, they represent one thing: a new type called an animal. Some condition determines which particular kind of animal a zookeeper might be dealing with.
A social network consists of different kinds of pages. Some pages represent individual humans, some places, some organizations, some might stand for activities, and so on. Collectively, they represent a new type: a social media page.
A notification application may report many kinds of events. Some are for email messages (which have many fields, as we’ve discussed), some are for reminders (which might have a timestamp and a note), some for instant messages (similar to an email message, but without a subject), some might even be for the arrival of a package by physical mail (with a timestamp, shipper, tracking number, and delivery note). Collectively, these all represent a new type: a notification.
We have used the word “data” above, but that’s actually been a bit of a lie. As we said earlier, data are how we represent information in the computer. What we’ve been discussing above is really different kinds of information, not exactly how they are represented. But to write programs, we must wrestle concretely with representations. That’s what we will do now, i.e., actually show data representations of all this information.
The song’s name, which is a
The song’s singer, which is also a
The song’s year, which is a
data ITunesSong: song(name, singer, year) end
ITunesSongWe follow a convention that types always begin with a capital letter.. The way we actually make one of these data is by calling
songwith three parameters; for instance:It’s worth noting that music managers that are capable of making distinctions between, say, Dance, Electronica, and Electronic/Dance, classify two of these three songs by a single genre: “World”.
song("La Vie en Rose", "Édith Piaf", 1945) song("Stressed Out", "twenty one pilots", 2015) song("Waqt Ne Kiya Kya Haseen Sitam", "Geeta Dutt", 1959)
lver = song("La Vie en Rose", "Édith Piaf", 1945) so = song("Stressed Out", "twenty one pilots", 2015) wnkkhs = song("Waqt Ne Kiya Kya Haseen Sitam", "Geeta Dutt", 1959)
In terms of the directory, structured data are no different from simple data. Each of the three definitions above creates an entry in the directory, as follows:
song("La Vie en Rose", "Édith Piaf", 1945)
song("Stressed Out", "twenty one pilots", 2015)
song("Waqt Ne Kiya Kya Haseen Sitam","Geeta Dutt", 1959)
data ITunesSong: song(name :: String, singer :: String, year :: Number) end
lver :: ___ = song("La Vie en Rose", "Édith Piaf", 1945)
ITunesSong. Therefore, we should write
lver :: ITunesSong = song("La Vie en Rose", "Édith Piaf", 1945)
Do Now!What happens if we instead write this?
lver :: String = song("La Vie en Rose", "Édith Piaf", 1945)What error do we get? How about if instead we write these?
lver :: song = song("La Vie en Rose", "Édith Piaf", 1945) lver :: 1 = song("La Vie en Rose", "Édith Piaf", 1945)Make sure you familiarize yourself with the error messages that you get.
dataconstruct in Pyret also lets us create conditional data, with a slightly different syntax. For instance, say we want to define the colors of a traffic light:
data TLColor: | Red | Yellow | Green end
|(pronounced “stick”) introduces another option. You would make instances of traffic light colors as
Red Green Yellow
data Animal: | boa(name :: String, length :: Number) | armadillo(name :: String, liveness :: Boolean) end
b1 = boa("Ayisha", 10) b2 = boa("Bonito", 8) a1 = armadillo("Glypto", true)
How would you annotate the three variable bindings?
Notice that the distinction between boas and armadillos is lost in the annotation.
b1 :: Animal = boa("Ayisha", 10) b2 :: Animal = boa("Bonito", 8) a1 :: Animal = armadillo("Glypto", true)
data ITunesSong: song(name, singer, year) end
data ITunesSong: | song(name, singer, year) end
take apart the fields of a structured datum, and
tell apart the variants of a conditional datum.
As we’ll see, Pyret also gives us a convenient way to do both together.
ITunesSong) and produces (a
Number). This gives us a rough skeleton for the function:
2016 - <get the song year>
.followed by a field’s name—
s(the parameter to
fun song-age(s :: ITunesSong) -> Number: 2016 - s.year end
fun song-age(s :: ITunesSong) -> Number: 2016 - s.year where: song-age(lver) is 71 song-age(so) is 1 song-age(wnkkhs) is 57 end
casesexpression has several branches: exactly as many as there are in the data definition. Each branch corresponds to one of the variants. Thus, if we wanted to compute advice for a driver based on a traffic light’s state, we might write:
fun advice(c :: TLColor) -> String: cases (TLColor) c: | Red => "wait!" | Yellow => "get ready..." | Green => "go!" end end
casesis followed by the name of the conditionally-defined type in parentheses (here,
TLColor), and then an expression that computes a value of that type (in this case,
cis already bound to such a value). Each variant is followed by
=>, and then an expression that computes an answer for that variant.
What happens if you leave out the
What if you leave out a variant? Leave out the
Redvariant, then try both
In this example, the variants had no fields. But if the variant has
fields, Pyret expects you to list names of variables for those fields,
and will then automatically bind those variables—
.-notation to get the field values.
Animalis conditionally defined, we know that we are likely to want a
casesto pull it apart; furthermore, we should give names to each of the fields:Note that the names of the variables do not have to match the names of fields. Conventionally, we give longer, descriptive names to the field definitions and short names to the corresponding variables.
cases (Animal) a: | boa(n, l) => ... | armadillo(n, l) => ... end
n, giving us the complete function:
fun animal-name(a :: Animal) -> String: cases (Animal) a: | boa(n, l) => n | armadillo(n, l) => n end where: animal-name(b1) is "Ayisha" animal-name(b2) is "Bonito" animal-name(a1) is "Glypto" end
boa("Bonito", 8)is a value. In the same way as we substitute simple data types like strings and numbers for parameters when we evaluate a function, we do the same thing here. After substituting, we are left with the following expression to evaluate:
cases (Animal) boa("Bonito", 8): | boa(n, l) => n | armadillo(n, l) => n end
Next, Pyret determines which case matches the data (the first one, for
boa, in this case). It then substitutes the field names with the
corresponding components of the datum result expression for the
matched case. In this case, we will substitute uses of
"Bonito" and uses of
8. In this program,
the entire result expression is a use of
n, so the result of
the program in this case is