Types Lab
In this lab we’ll explore the idea of functional programming and data types. In functional programming, we think of functions as transformations. They take any number of parameters and they change them into a return value.
Remember the structure of a Python function?
def double(number):
return 2 * number
Here,
number
is the paramter and2 * number
is the return value.As its name suggests, this function returns twice,
number
.
Here is a visualization of the function double
. We are not so focused on how the function gets its
work done as we are on what goes in and what comes out:
This unlocks a powerful new way of breaking down problems: we can think about how functions could be connected together. How do you quadruple a number? Just double it, and then double the result:
def quadruple(number):
return double(double(number))
You will soon see that much more difficult problems can often be broken down into simpler functions by thinking about how each function transforms an input into an output.
Thinking about types
You have already met a bunch of different kinds of objects in Python: 1
,
2.55
, True
, "pizza"
, None
. Each of these has a type. When we
talk about what goes in to functions and what comes out, we won’t talk about
particular inputs.
Who cares if you double
7
or9
? The point is you can’t doubleFalse
.
We’ll talk about the types of objects that functions operate on. Everything has a type. Here are a few types that you already know:
Type | Examples | Description |
---|---|---|
int |
145, -200 | Integers, just like you know them from math class :) |
float |
1.1115, 9.08 | Decimal numbers. They’re called float because of how they are stored |
str |
“apple”, “x” | Strings, or sequences of characters. Careful! 5 is an int but "5" is a str . |
bool |
True , False |
Booleans, or True/False values. Named for George Boole, who spent a lot of time thinking about them. |
list |
[1, 2, 3] |
A list holds other objects. |
✏️
Open ~/Desktop/making_with_code/pedprog/unit01/lab00/types_notes.md
.
For each function in the table below, describe what type the function would take as an input,
and what type the function would return as output.
Function | Input type(s) | Output type |
---|---|---|
Square root of a number | float |
float |
Multiplication of two integers | ||
Average of a list of numbers | ||
A function that gives all the factors of a number | ||
A function to check if a word is a curse word | ||
A function that assigns a friendliness score to a sentence | ||
A function that takes a website URL and returns the page content | ||
A function that counts the number of sentences in a paragraph | ||
> |
||
not |
⚡✨
When you finish, save your work, commit it, and push your changes so that Chris can see them.
The basic workflow is still the same:
git status
to determine what has changegit add
to add the files you’ve editedgit commit
to write a message about what you’ve changedgit push
to push your changes to GithubFrom now on, Chris will see your progress by checking what has been committed.
Building sentences with grammar
You already know how to simplify arithmetic expressions like (2 + 10) / (5 - 2)
by applying functions:
In this lab, we’re going to think about language in the same way. Instead of
functions like add
and subtract
, which combine numbers, we will use
functions like noun_phrase
, which combines words into parts of speech.
Instead of types like int
and bool
, we will be using new types like Verb
or NounPhrase
. (You’ll learn how to create new types in the next unit.)
Here’s an example of how we can use these functions to build a sentence. These functions are very strict about what kinds of inputs they accept, because we want to make sure we don’t create ill-formed sentences like “The my mouse hungry milk milk milk.”
Checking types in Python
In order to write these functions in Python, we will need to check the types of
each function’s inputs. We will use the built-in functions isinstance
and type
to check types, and assert
to intentionally crash the program when a function
receives the wrong type.
isinstance(thing, a_type)
checks whether thing
is a_type
, returning True
or False
. Types exist in a hierarchy: PluralNoun
is a particular kind of
Noun
, and Noun
is a particular kind of NounPhrase
. When you use
isinstance
, you are checking whether thing
belongs to a_type
or any of its
subtypes. If you want to check whether thing
’s type exactly, use type
to get
its type. For example:
>>> food = PluralNoun("potatoes")
>>> isinstance(food, PluralNoun)
True
>>> isinstance(food, Noun)
True
>>> isinstance(food, VerbPhrase)
False
>>> type(food)
<class 'PluralNoun'>
>>> type(food) == PluralNoun
True
>>> type(food) == Noun
False
assert condition
will do nothing if condition
is True
, but it will crash
the program if condition
is False
. Try it out–Python will keep going after
an error in interactive mode, but an uncaught error will crash a script.
>>> assert 1 + 2 == 3
>>> assert 1 + 2 == 4
AssertionError
>>> assert isinstance(13, int)
>>> assert isinstance("cowboy", int)
AssertionError
With these tools, we can start writing grammar functions like the ones we saw above. For example:
def noun_phrase(adjective, np):
assert isinstance(adjective, Adjective)
assert isinstance(np, NounPhrase)
return NounPhrase(adjective + ' ' + np)
This is what you will do next.
Auto-poetry
Love poems
This lab’s repo contains four Python files:
grammar.py
has a bunch of functions for combining and transforming grammatical types. These are unfinished; it’s your job to write them.poetry.py
has functions for writing beautiful auto-poems. Oncegrammar.py
is complete, you will be able to generate poems. You don’t need to edit this file.vocabulary.py
has a bunch of functions for picking random words. This is explored in the extension activity.grammatical_types.py
defines types likeNoun
,Adjective
, andTransitiveVerb
. You won’t edit this file, but it is useful as a reference for parts of speech.
💻
Open grammar.py
in Atom. This module is full of functions
which transform parts of speech. Let’s look at the first function:
|
|
- The docstring (line 21) describes this function’s type signature, or what goes in and
what comes out.
pluralize
receives aNoun
and returns aPluralNoun
. - Line 22 checks the input type. If it’s not a
Noun
, the program crashes. - Line 23 contains a conditional checking the final letters of the noun. Most English nouns are pluralized by adding “s” (“tree” becomes “trees”). But nouns ending in “s”, “ch”, “sh”, or “x” are pluralized by adding “es” (“beach” becomes “beaches”).
- Lines 24 and 26 create a new
PluralNoun
using the appropriate ending. All the grammatical types can be combined like normal strings using the+
operator.
💻
Now open poetry.py
in Atom. Let’s look at love_poem
:
|
|
- The docstring (line 2) says this function takes no inputs and returns a
Poem
. pluralize
is already finished, and it’s the only function other thanrandom_word
we’ll need. So this function will work!- See if you can predict what this function will do.
💻
Let’s try it out! Run python -i poetry.py
to load the
poetry
module. Now let’s have some love poems!
👾 💬
Runningpython
with the-i
flag means that you want to enter the interactive mode after running the module. This is useful, as in the example below, when you want to play around with all the variables and functions defined in a module.
>>> print(love_poem())
Roses are red
Violets are blue
Evenings are poisonous
And so are you.
Couplet
Before we can run couplet
, the next kind of poem, we’ll need to implement a
few more grammar functions in grammar.py
. Your task is to complete each of the
following functions. Right now, they just raise a NotImplementedError
, which
crashes the program. Remove these and replace them with the needed code.
The sections below describe what each function should do.
noun_phrase
determine_noun_phrase
make_definite
make_indefinite
💻 noun_phrase
>>> noun_phrase(Adjective("spooky"), Noun("closet"))
"spooky closet"
- Check that the first argument is an
Adjective
. - Check that the second argument is a
NounPhrase
. - Return a
NounPhrase
containing the adjective added to the noun (don’t forget a space between them).
💻 determine_noun_phrase
>>> determine_noun_phrase(Determiner("that"), Noun("eagle"))
"that eagle"
- Check that the first argument is a
Determiner
. - Check that the second argument is a
NounPhrase
. - Return a
DeterminedNounPhrase
containing the determiner added to the noun phrase (again, don’t forget a space between them).
💻 make_definite
>>> make_definite(NounPhrase("evil squirrel"))
"the evil squirrel"
- Check that the first argument is a
NounPhrase
. - Use
determine_noun_phrase
to combine the noun phrase withDeterminer("the")
. - Return the
DeterminedNounPhrase
you created.
💻 make_indefinite
>>> make_indefinite(NounPhrase("evil squirrel"))
"an evil squirrel"
>>> make_indefinite(NounPhrase("squirrel"))
"a squirrel"
- Check that the first argument is a
NounPhrase
. - Check whether the noun phrase starts with a vowel sound
(using
starts_with_vowel_sound
, described above).- If so, use
Determiner("an")
- If not, use
Determiner("a")
- If so, use
- Use
determine_noun_phrase
to combine the noun phrase with the determiner. - Return the
DeterminedNounPhrase
you created.
💻
Once these functions are finished, run python -i poetry.py
again and try out the couplet
function.
>>> print(couplet())
⚡✨
Once you havecouplet
working, it’s a good time to check in your work. Rungit status
to confirm that onlygrammar.py
has changed. Rungit diff
to see the changes you’ve made. If everything looks right,git add grammar.py
to add this file to the commit you’re preparing. Finally,git commit
, write your commit message, andgit push
to push your commit to the server.
Limerick
There’s one more kind of poem, a limerick. We’ll need to implement a few more grammar rules. Your group will need to complete the following functions:
verb_phrase
pase_tense_transitive
past_tense_intrasitive
verb_phrase_intrasitive
💻 verb_phrase
>>> verb_phrase(Adverb("easily"), VerbPhrase("crushed her enemies"))
"easily crushed her enemies"
- Check that the first argument is an
Adverb
. - Check that the second argument is a
VerbPhrase
. - Combine them into a
VerbPhrase
. - Return the
VerbPhrase
you created.
💻 past_tense_transitive
>>> past_tense_transitive(VerbTransitive("avoid"))
"avoided"
- Check that the first argument is a
TransitiveVerb
. Make sure it’s not aPastTenseTransitiveVerb
or you might accidentally conjugate a verb twice, resulting in something like “avoideded”. - Choose an appropriate ending to conjugate the verb in the past tense. If the verb ends in ’e’, add ’d’. Otherwise, add ’ed'.
- This won’t work for irregular verbs (e.g. the past tense of ‘go’ is ‘went’), but let’s just ignore that for now. You can come back later and add logic for some irregular verbs if you’re feeling ambitious.
- Return a
PastTenseTransitiveVerb
consisting of the verb plus its new ending.
💻 past_tense_intransitive
>>> past_tense_intransitive(VerbTransitive("molt"))
"molted"
- Check that the first argument is an
IntransitiveVerb
. Again, make sure it’s not aPastTenseIntransitiveVerb
. - Choose an appropriate ending to conjugate the verb in the past tense. If the verb ends in ’e’, add ’d’. Otherwise, add ’ed’. Again, don’t worry about irregular verbs. The English language has only itself to blame for this mess.
- Return a
PastTenseIntransitiveVerb
consisting of the verb plus its new ending.
💻 verb_phrase_transitive
Intransitive verbs like “quit” are already verb phrases because they can form complete sentences (I quit). But transitive verbs like “take” need a noun phrase. You can’t just take, you have to take something.
>>> verb = past_tense_transitive(TransitiveVerb("transform"))
>>> np = make_definite(noun_phrase(Adjective("evil"), Noun("squirrel")))
>>> verb_phrase_transitive(verb, np)
"transformed the evil squirrel"
- Check that the first argument is a
TransitiveVerb
. - Check that the second argument is a
NounPhrase
. - Return a VerbPhrase consisting of the verb and the noun phrase.
💻
Once these functions are finished, run python -i poetry.py
again and try out the limerick
function. Note that limerick
has three
arguments, a name and two pronouns.
>>> print(limerick("Alex", "he", "his"))
⚡✨
Once you havelimerick
working, commit your changes and push them to the server.
If you found this lab interesting, you might be interested in exploring computational linguistics, or using CS to explore how language works. This lab just scratched the tiniest layer of the surface of this wonderful field. Here, we generated sentences. What about the reverse, trying to understand language produced by humans?
Extention: A New Kind of Poem
Let’s take a look at poetry.py
. Each poem in poetry.py
is a function utilizing the functions from grammar.py
(which you’ve seen) and vocabulary.py
(which you haven’t). The documentation below explains how to use the functions in vocabulary.py
. To play with the examples, run
python -i vocabulary.py
to open a Python shell and load the vocabulary
module.
💻 Use these functions to define a new kind of poem, or another kind of text.
Here are some possible ideas:
- A new verse for a song you like
- Detailed insults
- Birthday wishes
- Weird recipes
- Dreams or nonsense stories
random_word
Returns one or more random words, matching the specified conditions.
Arguments:
- word_type (type): Must be one of
Noun
,TransitiveVerb
,IntransitiveVerb
,Adjective
, orAdverb
. - count (int): Optional. How many words you want. Normally, the result of
random_word
is a word of the requested type, but whencount
is provided, the result is a list of such words. You’ll learn about lists in the next lab. - rhymes_with (string): Optional. A word the result should rhyme with.
- meter (Meter): Optional. The pattern of stresses that should be in the result’s
pronounciation. A Meter is a string of digits, where
1
represents a word’s main stress,2
represents secondary stress, and0
represents unstressed syllables. The meter used below,"1020"
, matches words whose sound is like “BUM-bah-Bum-bah.” Try saying ’territory’, ‘storyteller’, and ‘motorcycle’ out loud. TERR-i-Tor-y, STOR-y-Tell-er, MO-tor-Cy-cle. - syllables (int): Optional. The number of syllables that should be in the result.
Returns:
- A word of word_type. If
count
is provided, returns a list of such words.
>>> random_word(Noun)
'bayonet'
>>> random_word(Noun, rhymes_with="soup")
'loop'
>>> random_word(Noun, syllables=6)
'microorganism'
>>> random_word(Noun, count=3, meter=Meter("1020"))
['territory', 'storyteller', 'motorcycle']
>>> random_word(Noun, rhymes_with="orange")
NoWordError: Couldn't find a Noun with conditions: rhymes with orange
>>>
rhyming_pair
Returns two rhyming words of the specified types.
Arguments:
- first_word_type (type): Must be one of
Noun
,TransitiveVerb
,IntransitiveVerb
,Adjective
, orAdverb
. - second_word_type (type): Must be one of
Noun
,TransitiveVerb
,IntransitiveVerb
,Adjective
, orAdverb
. - meter (Meter): Optional. The pattern of stresses that should be in the result’s pronounciation.
- syllables (int): Optional. The number of syllables that should be in the result.
Returns:
- Two words of (first_word_type, second_word_type). See the example usage for how to capture each return value in a separate variable.
>>> rhyming_pair(TransitiveVerb, IntransitiveVerb, meter=Meter("10"))
('fumble', 'grumble')
>>> adj, noun = rhyming_pair(Adjective, Noun, syllables=2)
>>> adj
'graphic'
>>> noun
'traffic'
count_syllables
Counts the syllables in a word.
Arguments:
- word (str)
Returns:
- An int, the number of syllables
>>> count_syllables("bogus")
2
get_meter
Returns the meter (the pattern of stresses in pronunciation) for a word.
Arguments:
- word (str)
Returns:
- Meter, a string of digits representing stresses in the word’s pronunciation.
1
represents a word’s main stress,2
represents secondary stress, and0
represents unstressed syllables. The examples above show that the words are pronounced ‘A-ni-mal’ (‘100’), ‘HE-li-Cop-ter’ (‘1020’), and ‘In-ex-CUS-a-ble’ (‘20100’).
>>> get_meter("animal")
'100'
>>> get_meter("helicopter")
'1020'
>>> get_meter("inexcusable")
'20100'
starts_with_vowel_sound
Determines whether the word starts with a vowel sound.
Arguments:
- text (str)
Returns:
- bool,
True
when the first word oftext
starts with a vowel sound.False
otherwise.
>>> starts_with_vowel_sound("horrible hounds")
False
>>> starts_with_vowel_sound("awesome owls")
True
⚡✨
If you decide to do the extension, add your changes to a new commit and push them to the server. For this commit, you will have changes inpoetry.py
and maybe alsogrammar.py
.