1 11-TypeHints


Previous: 10-DebuggingTesting.html

1.1 Videos and Screencasts

1.2 Project sizes

https://informationisbeautiful.net/visualizations/million-lines-of-code/
11-TypeHints/2552_Lines_of_code_Dec18_FB.png

1.3 Big program, small program?

https://en.wikipedia.org/wiki/Programming_in_the_large_and_programming_in_the_small

1.4 Typing systems

1.4.1 Dynamic versus static typing

For large projects, why is static typing often better than dynamic typing?
* How do you know what your function returns?
* How do you know what types of object a function needs as arguments?
* How do you learn 10,000,000 lines of code as quickly as possible?
* You’ve just gotten your first job/internship, and you boss says: “Here’s a giant project, fix a bug add a feature, get it back to me by next week.”
* Static typing supports the human!
* The benefit is not necessarily in technical speed.
* Instead, it is good https://en.wikipedia.org/wiki/Human_factors_and_ergonomics which can be used to design a language that reduces the tendency for YOU to make an error tends to be a better one.
* You can re-factor to new versions of a language more easily.
* Typing catches an entire class of bugs that don’t need to exist at all!
* Helps code completion in IDEs too.

For small projects, what are the advantages of dynamic typing over static?
* For example, in python (dynamic) versus C++ (static), we more easily can write one function to operate on containers containing many types of thing.
* See 03-IntroPython for our previous discussion of “lines ratio” and expressiveness.
* The same program that might take you a week in Java or C++ could take you an hour in python.
* Human time is, in general, far more valuable that computer time (unless you aren’t a valuable employee…).
* The more prized employee saves their valuable time and implements their solution in python, then delegating to a grunt, it’s conversion to a faster language to a grunt.
* It’s the algorithmic decision making and design that is the deep, respected, and hard-to-come-by skill that get’s the big $$

1.4.2 Language-based auto-assistance

Another grand quote for the day:

**Mommy knows best.**
    - my daughter

1.4.3 Duck typing: Python’s type system

11-TypeHints/duck_typing.png
This moniker comes from the phrase “if it walks like a duck and it quacks like a duck, then it must be a duck” (or any of its variations).
* https://en.wikipedia.org/wiki/Type_system#Duck_typing
* https://en.wikipedia.org/wiki/Duck_typing

As an example, you can call len() on any Python object that defines a special ._len_() method:
11-TypeHints/Ducky.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-

class Ducky:
    def _len_(self) -> int:
        return 95022

a_duck = Ducky()
print(len(a_duck))
#!/usr/bin/python3
# -*- coding: utf-8 -*-

def len(obj):
    return obj._len_()

1.4.4 What is the type of my object in python?

11-TypeHints/whatisit.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-

thing = "5"

# Just get type
# type(object)
print(type(thing))

# Check type and subtypes
if isinstance(thing, str):
    print("object is instance of str")

# To check if the type of o is exactly str (exclude subclasses):]
if type(thing) is str:
    print("object is str")

# The following also works, and can be useful in some cases:
if issubclass(type(thing), str):
    print("object is sub-class of str")

1.5 Python type hints

1.5.1 From the horse’s mouth

1.5.2 The official extensions and documentation

1.5.3 mypy actually checks the types

The primary reference implementation of type hinting is mypy:
http://mypy-lang.org/

Install it this way in Linux if you have sudo capability:
$ sudo zypper install python3-mypy mypy
$ sudo dnf install python3-mypy mypy
$ sudo apt install python3-mypy mypy

If you don’t have sudo privileges (or are not running Linux), install it this way:
$ pip3 install --upgrade mypy --user

This will check your script for type validity, and will do so with even more strictness than the defaults:
$ mypy --strict --disallow-any-explicit myscript.py
This is what we expect in this class!

1.5.4 Variations

mypy is the primary pre-run type-checker, but there are others:
* https://www.bernat.tech/the-state-of-type-hints-in-python/

Note:

Python <3.9

#!/usr/bin/python3

from typing import List

mylist: List[str] = ['thing', 'anotherthing']

Python >3.9

#!/usr/bin/python3

mylist: list[str] = ['thing', 'anotherthing']

1.5.5 Tutorials

1.6 Real world type hinting

1.7 Code examples

You can either write type-hints yourself, as you code, or have monkeytype write them for you after/during coding.

1.7.1 Manual typing

11-TypeHints/type_hint_00.py (correct)
11-TypeHints/type_hint_01.py (error)
11-TypeHints/type_hint_02.py (an un-typed function)

Perform pre-execution type checks as:
$ mypy type_hint_0n.py

$ mypy --disallow-untyped-defs type_hint_0n.py
$ mypy --disallow-any-explicit type_hint_0n.py
$ mypy --strict type_hint_0n.py

We will require this in all code from the time we give you the class VM!
$ mypy --strict --disallow-any-explicit *.py

1.7.2 Auto-generating

To auto-generate type hints on existing code base:

1.7.2.1 To generate actual code or merely annotating stubs

https://monkeytype.readthedocs.io/en/latest/index.html

1.7.2.1.1 How to use monkeytype to type your code for you
  1. Write your code without types (or with some if you want).
    11-TypeHints/file_to_type.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

def my_func(x):
    return [x]

def main():
    x = 5
    my_func(x)

if _name_ == "_main_":
    main()
  1. Make a trivial wrapper that when executed runs all your code
    11-TypeHints/temp_wrapper.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import file_to_type

file_to_type.main()
  1. Run the wrapper, and then apply it:
#!/bin/bash

# To show it is not typed:
mypy --strict --disallow-any-explicit file_to_type.py

monkeytype run temp_wrapper.py
monkeytype apply file_to_type

# Run again to show it's typed now!
mypy --strict --disallow-any-explicit file_to_type.py

monkeytype actually watches the values of the variables passed to your functions as the code runs (it’s not just a static analysis).

  1. Check out your new typed python file!
    11-TypeHints/file_to_type_after.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

# python <3.9
# from typing import List

# python <3.9
#def my_func(x: int) -> List[int]:
def my_func(x: int) -> list[int]:
    return [x]

def main() -> None:
    x: int = 5
    my_func(x)

if _name_ == "_main_":
    main()

Now, type-check it again:

#!/bin/bash

# To show it is correctly typed now:
mypy --strict --disallow-any-explicit file_to_type.py

1.7.2.2 If you just want to generate annotation stubs only

stubgen myscript.py
Will create out/myscript.pyi with types specified separately
Note: we don’t bother with stubs in this class.

++++++++++++++++++
Cahoot-11.1
https://mst.instructure.com/courses/58101/quizzes/56078

++++++++++++++++++
Cahoot-11.2
https://mst.instructure.com/courses/58101/quizzes/56079

1.7.3 Example 4: hangman

The example child’s game from the below book, we covered last class, now typed and much easier to understand at quick glance:
https://en.wikipedia.org/wiki/Hangman_(game)
http://inventwithpython.com/invent4thed/chapter7.html
http://inventwithpython.com/invent4thed/chapter8.html
http://inventwithpython.com/invent4thed/chapter9.html

11-TypeHints/hangman.py (typed using python >3.9 type hints)

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import random

HANGMAN_PICS: list[str] = [
    """
  +---+
      |
      |
      |
     ===""",
    """
  +---+
  O   |
      |
      |
     ===""",
    """
  +---+
  O   |
  |   |
      |
     ===""",
    """
  +---+
  O   |
 /|   |
      |
     ===""",
    """
  +---+
  O   |
 /|\  |
      |
     ===""",
    """
  +---+
  O   |
 /|\  |
 /    |
     ===""",
    """
  +---+
  O   |
 /|\  |
 / \  |
     ===""",
]

words: list[str] = (
    "ant baboon badger bat bear beaver "
    "camel cat clam cobra cougar coyote crow deer dog "
    "donkey duck eagle ferret fox frog goat goose hawk "
    "lion lizard llama mole monkey moose mouse mule newt "
    "otter owl panda parrot pigeon python rabbit ram rat "
    "raven rhino salmon seal shark sheep skunk sloth "
    "snake spider stork swan tiger toad trout turkey "
    "turtle weasel whale wolf wombat zebra"
).split()

# words: list[str] = [
#     "ant",
#     "baboon",
#     "badger",
#     "bat",
#     "bear",
#     "beaver",
#     "camel",
#     "cat",
#     "clam",
#     "cobra",
#     "cougar",
#     "coyote",
#     "crow",
#     "deer",
#     "dog",
#     "donkey",
#     "duck",
#     "eagle",
#     "ferret",
#     "fox",
#     "frog",
#     "goat",
#     "goose",
#     "hawk",
#     "lion",
#     "lizard",
#     "llama",
#     "mole",
#     "monkey",
#     "moose",
#     "mouse",
#     "mule",
#     "newt",
#     "otter",
#     "owl",
#     "panda",
#     "parrot",
#     "pigeon",
#     "python",
#     "rabbit",
#     "ram",
#     "rat",
#     "raven",
#     "rhino",
#     "salmon",
#     "seal",
#     "shark",
#     "sheep",
#     "skunk",
#     "sloth",
#     "snake",
#     "spider",
#     "stork",
#     "swan",
#     "tiger",
#     "toad",
#     "trout",
#     "turkey",
#     "turtle",
#     "weasel",
#     "whale",
#     "wolf",
#     "wombat",
#     "zebra",
# ]

def getRandomWord(wordList: list[str]) -> str:
    # This function returns a random string from the passed list of strings.
    wordIndex = random.randint(0, len(wordList) - 1)
    return wordList[wordIndex]

def displayBoard(missedLetters: str, correctLetters: str, secretWord: str) -> None:
    print(HANGMAN_PICS[len(missedLetters)])
    print()

    print("Missed letters:", end=" ")
    for letter in missedLetters:
        print(letter, end=" ")
    print()

    blanks = "_" * len(secretWord)

    # replace blanks with correctly guessed letters
    for i in range(len(secretWord)):
        if secretWord[i] in correctLetters:
            blanks = blanks[:i] + secretWord[i] + blanks[i + 1 :]

    # show the secret word with spaces in between each letter
    for letter in blanks:
        print(letter, end=" ")
    print()

def getGuess(alreadyGuessed: str) -> str:
    # Returns the letter the player entered.
    # This function makes sure the player entered a single letter,
    # and not something else.
    while True:
        print("Guess a letter.")
        guess = input()
        guess = guess.lower()
        if len(guess) != 1:
            print("Please enter a single letter.")
        elif guess in alreadyGuessed:
            print("You have already guessed that letter. Choose again.")
        elif guess not in "abcdefghijklmnopqrstuvwxyz":
            print("Please enter a LETTER.")
        else:
            return guess

def playAgain() -> bool:
    # This function returns True if the player wants to play again;
    # otherwise, it returns False.
    print("Do you want to play again? (yes or no)")
    return input().lower().startswith("y")

def main() -> None:
    print("H A N G M A N")
    missedLetters = ""
    correctLetters = ""
    secretWord = getRandomWord(words)
    gameIsDone = False

    while True:
        displayBoard(missedLetters, correctLetters, secretWord)

        # Let the player enter a letter.
        guess = getGuess(missedLetters + correctLetters)

        if guess in secretWord:
            correctLetters = correctLetters + guess

            # Check if the player has won.
            foundAllLetters = True
            for i in range(len(secretWord)):
                if secretWord[i] not in correctLetters:
                    foundAllLetters = False
                    break
            if foundAllLetters:
                print('Yes! The secret word is "' + secretWord + '"! You have won!')
                gameIsDone = True
        else:
            missedLetters = missedLetters + guess

            # Check if player has guessed too many times and lost.
            if len(missedLetters) == len(HANGMAN_PICS) - 1:
                displayBoard(missedLetters, correctLetters, secretWord)
                print(
                    "You have run out of guesses!\nAfter "
                    + str(len(missedLetters))
                    + " missed guesses and "
                    + str(len(correctLetters))
                    + ' correct guesses, the word was "'
                    + secretWord
                    + '"'
                )
                gameIsDone = True

        # Ask the player if they want to play again (but only if the game is done).
        if gameIsDone:
            if playAgain():
                missedLetters = ""
                correctLetters = ""
                gameIsDone = False
                secretWord = getRandomWord(words)
            else:
                break

if _name_ == "_main_":
    main()

How would you design unit tests for the functions in this game?
How would you design integration tests for the main in this game?

1.8 Lol

Python Type Hints are Turing Complete
Ori Roth
Grigore showed that Java generics are Turing complete by describing a reduction from Turing machines to Java subtyping. We apply Grigore’s algorithm to Python type hints and deduce that they are Turing complete. In addition, we present an alternative reduction in which the Turing machines are simulated in real time, resulting in significantly lower compilation times. Our work is accompanied by a Python implementation of both reductions that compiles Turing machines into Python subtyping machines.
Subjects: Programming Languages (cs.PL)
Cite as: arXiv:2208.14755 [cs.PL]
(or arXiv:2208.14755v1 [cs.PL] for this version)
https://doi.org/10.48550/arXiv.2208.14755
https://arxiv.org/abs/2208.14755
https://news.ycombinator.com/item?id=32779296

Next: 12-Strings.html