0. Types

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 and 2 * 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:

Functional view of the double function

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:

Functional view of the quadruple function

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 or 9? The point is you can’t double False.

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 change
  • git add to add the files you’ve edited
  • git commit to write a message about what you’ve changed
  • git push to push your changes to Github

From 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:

Functional view of arithmetic

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.)

Functional view of syntax

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.”

Building sentences

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. Once grammar.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 like Noun, Adjective, and TransitiveVerb. 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:

20
21
22
23
24
25
26
def pluralize(noun):
    "Noun -> PluralNoun"
    assert type(noun) == Noun
    if noun.endswith("s") or noun.endswith("ch") or noun.endswith("sh") or noun.endswith("x"):
        return PluralNoun(noun + 'es')
    else:
        return PluralNoun(noun + 's')
  • The docstring (line 21) describes this function’s type signature, or what goes in and what comes out. pluralize receives a Noun and returns a PluralNoun.
  • 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:

24
25
26
27
28
29
30
31
def love_poem():
    "() -> Poem"
    poem = Poem()
    poem.append("roses are red")
    poem.append("violets are blue")
    poem.append(pluralize(random_word(Noun)) + " are " + random_word(Adjective))
    poem.append("and so are you.")
    return 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 than random_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!

👾 💬
Running python 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 with Determiner("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")
  • 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 have couplet working, it’s a good time to check in your work. Run git status to confirm that only grammar.py has changed. Run git 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, and git 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 a PastTenseTransitiveVerb 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 a PastTenseIntransitiveVerb.
  • 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 have limerick 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, or Adverb.
  • count (int): Optional. How many words you want. Normally, the result of random_word is a word of the requested type, but when count 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, and 0 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, or Adverb.
  • second_word_type (type): Must be one of Noun, TransitiveVerb, IntransitiveVerb, Adjective, or Adverb.
  • 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, and 0 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 of text 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 in poetry.py and maybe also grammar.py.