Functional Programming with Grammar #
In this challenge, we’re going to think about language with a functional programming lens. Instead of
functions like add and subtract, which combine numbers, we will use
functions like noun_phrase, which combines words into parts of speech.
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.”
Part 3: Auto-poetry #
🌐 Github Repo: github.com/Making-with-Code/lab_types
3a: Love poems #
The repository you cloned contains four Python files:
- grammar.pyhas a bunch of functions for combining and transforming grammatical types. These are unfinished; it’s your job to write them.
- poetry.pyhas functions for writing beautiful auto-poems. Once- grammar.pyis complete, you will be able to generate poems.
- vocabulary.pyhas a bunch of functions for picking random words. This is explored in the extension activity.
- grammatical_types.pydefines 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:
|  |  | 
- The docstring (line 2) describes this function’s type signature, or what goes in and
what comes out. pluralizereceives aNounand returns aPluralNoun.
- Line 3 checks the input type. If it’s not a Noun, the program crashes.
- Line 4 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”, or “sh” are pluralized by adding “es” (“beach” becomes “beaches”).
- Lines 5 and 7 create a new PluralNounusing the appropriate ending. All the grammatical types are subclasses ofstr, so they 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.
- pluralizeis already finished, and it’s the only function other than- random_wordwe’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!
>>> print(love_poem())
Roses are red
Violets are blue
Evenings are poisonous
And so are you.
3b: Couplet #
Before we can run couplet, the next kind of poem, we’ll need to implement a
few more grammar rules in grammar.py. Your group will need to complete the following functions:
- noun_phrase
- determine_noun_phrase
- make_definite
- make_indefinite
✏️ Before coding, create a function diagram with an example use case for each grammar rule in your group’s Google Slide.
💻 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 NounPhrasecontaining 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 DeterminedNounPhrasecontaining 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_phraseto combine the noun phrase withDeterminer("the").
- Return the DeterminedNounPhraseyou 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_phraseto combine the noun phrase with the determiner.
- Return the DeterminedNounPhraseyou created.
💻 
 Once these functions are finished, run python -i poetry.py
again and try out the couplet function.
>>> print(couplet())
3c: 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
✏️ Before coding, create a function diagram with an example use case for each grammar rule in your group’s Google Slide.
💻 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 VerbPhraseyou created.
💻 past_tense_transitive
>>> past_tense_transitive(VerbTransitive("avoid")
"avoided"
- Check that the first argument is a TransitiveVerb. Make sure it’s not aPastTenseTransitiveVerbor 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 PastTenseTransitiveVerbconsisting 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 PastTenseIntransitiveVerbconsisting 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"))
Deliverables #
You now have a powerful set of tools to write auto-poetry. See what else you can come up with. We’ll share the best poems your computers are able to come up with!
For this lab, you should submit the following:
- The Git Repo
- Your Google Slide
By the way, 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 Generator #
Let’s take a look at poetry.py. Each poem is its own function utilizing the functions from grammar.py. The extension activity to create a new phrase generator of your creation. It can be anything from a haiku generator, to a compliement generater, to an insult generator. It’s up to you!
💻 
 Create a new type of phrase generator by implementing the grammar rules in grammar.py and the random word functionality in vocabulary.py. Be sure to reference poetry.py for syntax and formatting tips.
Random Words #
Takea look at the functions provided in vocabulary.py. Run python -i vocabulary.py. This loads everything in vocabulary and then enters interactive mode. Try the following. (You’ll get different results, of course, because they are random.)
>>> 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
>>>
Whoa! random_word is super powerful. Here is random_word’s type signature:
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_wordis a word of the requested type, but whencountis provided, the result is a list of such words.
- 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 1represents a word’s main stress,2represents secondary stress, and0represents unstressed syllables. The meter used above,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. Ifcountis provided, returns a list of such words.
There are some other useful functions in vocabulary.py:
