Добровольная типизация в Python 3

(и не только)

 

Максим Кольцов

10 декабря 2018 г.

@maksbotan

Обо мне

  • Мат-Мех СПбГУ
  • Pascal, C++, Java, Python, Haskell
  • Serokell
  • Biocad
  • Центр для одаренных школьников Интеллект

План

  • История типов и языков программирования
  • Современные тенденции
  • Зачем могут быть нужны типы
  • Использование Python type hints
    • Синтаксис
    • Полезные приемы
    • Инструменты
  • Сложные системы типов в других языках
  • BONUS LEVEL

Вдохновение

Проблема?

Что такое тип

Система типов — это гибко управляемый синтаксический метод доказательства отсутствия в программе определенных видов поведения при помощи классификации выражений языка по разновидностям вычисляемых ими значений.

Бенджамин Пирс

«Типы в языках программирования»

История типов

  • 1936, Алонзо Чёрч: лямбда-исчисление
  • 1936, Алан Тьюринг: машина Тьюринга
  • 1940, Алонзо Чёрч: простое типизированное лямбда-исчисление

David Hilbert

Alonzo Church

Alan Turing

История типов

  • 1972, Жан-Ив Жирар: System F, System Fω
  • 1974, Джон Рейнольдс: полиморфное лямбда-исчисление
  • 1973, Пер Мартин-Лёф: теория типов Мартина-Лёфа

Jean-Yves Girard

John C. Reynolds

Per Martin-Löf

История типов

  • 1969, Роджер Хиндли,
    1978, Робин Милнер: алгоритм Хиндли-Милнера
  • 1979: язык ML

J. Roger Hindley

Robin Milner

История типов

  • 1985, Thierry Coquand, Gérard Huet: Calculus of Constructions, Coq
  • 2013, Coquand, Martin-Löf, Воеводский и другие: Homotopy Type Theory

Thierry Coquand

Coq

История типов

  • 2006, Jeremy Siek: Gradual Typing

Jeremy Siek

Язык программирования бога?

Philip Wadler

Что происходит сейчас

  • 2012: TypeScript
  • 2014: JavaScript Flow
  • 2014: PEP-483: Type Hints
  • 2015: PHP 7.0
  • 2010: Rust
  • 201x: Go 2.0 (G E N E R I C S)

Что типы нам дают

Типобезопасность
(корректность, type safety, soundness):

правильно типизированные выражения не ломаются

  • Продвижение (progress): правильно типизированное выражение является значением или его можно вычислить
  • Сохранение (preservation): результат вычисления правильно типизированного выражения тоже правильно типизирован

Что может пойти не так

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. ... My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Tony Hoare

А что питон?

PEP-3107: аннотация функций, 2006  г., Python 3.0

def add(a: int, b: int) -> int:
    ...

PEP-526: аннотация переменных, 2016 г., Python 3.6

a: int = 5
b: int = 10
print(add(a, b))

PEP-484: Type Hints, 2014 г.

from typing import List, Optional, TypeVar

T = TypeVar('T')
def head(xs: List[T]) -> Optional[T]:
    ...

PEP-483: The Theory of Type Hints, 2014 г.

И как этим пользоваться?

Система типов mypy

  • Нет аннотаций — нет проверки
  • Любой наследник type — тип: bool, int, str, object, Decimal, MyClass...
  • Вывод типов:
a = 5
# У a тип int
b = 'abc'
# У b тип str
c = [42]
# У c тип List[int]
a = 5
a = 'abc' # Ошибка!
a = []
# ???
  • reveal_type

List[T] ???

Generics — "функции на типах"

from typing import List

a: List[int] = [1,2,3]
b: int = a[0] # OK!
c: str = a[1] # NO!

Положить и достать

d: str = 'abc'
a.append(d) # NO!

e: float = a[0] # OK?
f: float = 2.7
a.append(f) # HMMM...

Обман

g: Any = a[2] # OK
h: Any = 'BAM!'
a.append(h) # OK!

Ко-ко-ко

S

T

List[S]

List[T]

?

<:

FrozenSet[S]

?

FrozenSet[T]

Writer[S]

?

Writer[T]

Ко-ко-ко

S

T

List[S]

List[T]

<:

FrozenSet[S]

<:

FrozenSet[T]

Writer[S]

Writer[T]

:>

:(

Функции?

S

T

<:

U

V

<:

Callable[[U], S]

?

Callable[[V], T]

Ещё полезности

Union[T1, T2, ...]

def foo(a: Union[bool, str]) -> None:
    if isinstance(a, bool):
        print(a is True)
    else:
        print(a == 'abc')

Tuple[T1, T2, ...]

def foo(a: Tuple[bool, str]) -> None:
    (b, s) = a
    print(b is True, s == "abc")

Sequence[T], Iterator[T], Iterable[T], Sized, Container[T]...

Ограниченные типовые переменные:

from typing import TypeVar

AnyStr = TypeVar('AnyStr', bytes, str)

def foo(a: AnyStr, b: AnyStr) -> AnyStr:
    return a + b

Как найти то, чего нет?

foo = {'*': 42}
a = foo.get('?') # type: ???

Optional[Int]!

a: Optional[Int] = foo.get('?')
print(a * 5) # NO!
a: Optional[Int] = foo.get('?')
if a is not None:
    print(a * 5) # type: int
else:
    print('Sad!')

Новые имена вещей

from my_library import get_angle
alpha: float = get_angle(x, y)
from someones_library import rotate
def rotate( coord: Tuple[float, float]
          , alpha: float
          ) -> Tuple[float, float]: ...
print(rotate((x1, y1), alpha)

Новые имена вещей

from typing import NewType

Degrees = NewType('Degrees', float)
Radians = NewType('Radians', float)
def degrees(a: Radians) -> Degrees: ...
def radians(a: Degrees) -> Radians: ...
angle: Degrees = get_angle()
def rotate(..., angle: Radians): ...
print(rotate((x1, y1), angle) # NO!

Новые имена вещей

def length(vector: Tuple[float, float]) -> float:
    return sqrt(vector[0]**2 + vector[1] ** 2)
my_age = 24
my_height = 183
print(length((my_age, my_height)) # WAT?
from typing import NamedTuple
class Vector(NamedTuple):
    x: float
    y: float
def length(vector: Vector) -> float:
    ...

Any namedtuple can be accidentally compared to any other with the same number of fields.

Dataclasses!

from dataclasses import dataclass

@dataclass
class Vector():
    x: float
    y: float
  • Метод __init__ из коробки
  • Все методы сравнения
  • Типобезопасное сравнение с любыми другими объектами
  • Опциональная иммутабельность
  • Значения по умолчанию
  • Настраиваемый __repr__
  • Конвертация в кортежи и словари

Пример

@dataclass
class Contact:
    name: Name
    email: EmailInfo
    postal: PostalInfo

"Контакт должен содержать email или почтовый адрес"

@dataclass
class Contact:
    name: Name
    email: Optional[EmailInfo]
    postal: Optional[PostalInfo]
noone = Contact('noone', None, None)
# OOOPS...

Выход

AddressInfo = Union[
    EmailInfo,
    PostalInfo,
    Tuple[EmailInfo, PostalInfo]
]

@dataclass
class Contact():
    name: Name
    address: AddressInfo

Выход

def make_contact( name  : Name
                , email : Optional[EmailInfo]
                , postal: Optional[PostalInfo]
                ) -> Optional[Contact]:
    if email is None and postal is None:
        return None
    if email is not None:
        return Contact(name, email)
    elif postal is not None:
        return Contact(name, postal)
    else:
        return Contact(name, (email, postal))

Валидация раз и навсегда

@dataclass
class SuperSecretCreds():
    username: str
    password: str

def destroy_all(creds: SuperSecretCreds):
    if is_valid(creds):
        super_secret_api.boom(creds)
    else:
        raise Http403
def destroy_sneaky(creds: SuperSecretCreds):
    super_secret_api.boom(creds) # OOH...

Валидация раз и навсегда

ValidCreds = NewType('ValidCreds', SuperSecretCreds)
def boom(creds: ValidCreds) -> None:
    ...

def validate(creds: SuperSecretCreds)
    -> Optional[ValidCreds]:
    ...

def destroy_all(creds: SuperSecretCreds):
    validated: Optional[ValidCreds] = validate(creds)
    if validated is not None:
        super_secret_api.boom(validated)
    else:
        raise Http403

Tooling

Сложные системы типов

a: List[int] = [1, 2, 3]
print(a[42]) # BOOM!
def dot( a: List[float]
       , b: List[float]
       ) -> float:
    ...

a: List[float] = [1,2,3]
b: List[float] = [5,6]
print(dot(a, b)) # OOOPS...

Сложные системы типов

exactLength 
  :  (len : Nat) 
  -> (xs : Vect m a) 
  -> Maybe (Vect len a)
Main> :t printf "%d %s"
printf "%d %s" : Int -> String -> String

Сложные системы типов

x = tf.placeholder(
    tf.float32, [None, 784]
)
y = tf.placeholder(
    tf.float32, [None, 10]
)

W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

Сложные системы типов

type MNIST
  = Network
    '[ Convolution 1 10 5 5 1 1, Pooling 2 2 2 2, Relu
     , Convolution 10 16 5 5 1 1, Pooling 2 2 2 2, Reshape, Relu
     , FullyConnected 256 80, Logit, FullyConnected 80 10, Logit]
    '[ 'D2 28 28, 'D3 24 24 10, 'D3 12 12 10, 'D3 12 12 10
     , 'D3 8 8 16, 'D3 4 4 16, 'D1 256, 'D1 256
     , 'D1 80, 'D1 80, 'D1 10, 'D1 10]

randomMnist :: MonadRandom m => m MNIST
randomMnist = randomNetwork

Сложные системы типов

def sqrt(x: float) -> float:
    ...

print(sqrt(-1)) # HMMMM...
class NonEmpty(Generic[T], list):
    def __init__( self
                , h: T
                , t: List[T]
                ) -> None:
        self.extend([h] + t)

BONUS LEVEL

Типы как логика

\dfrac{A \quad (A \to B)}{B}

Modus Ponens:

Пусть А, B — утверждения

Значения — доказательства:

prfA: A = ...
prfB: B = ...prfA...

Какой тип у утверждения "из A следует B"?

def prfAiB(prfA: A) -> B:
    return ...prfA...
AiB = Callable[[A], B]
prfA: A = ...
prfB: B = prfAiB(prfA)

Простое доказательство

(A \land B \to C) \to \Bigl[A \to (B \to C)\Bigr]
AandB = Tuple[A, B]
def prf(prfAandBiC: Callable[[AandB], C])
    -> Callable[[A], Callable[[B], C]]:
    def prfAiBiC(prfA: A):
        def prfBiC(prfB: B):
            prfC: C = prfAandBiC(
                (prfA, prfB)
            )
            return prfC
        return prfBiC
    return prfAiBiC

Простое доказательство

(A \to B) \to \Bigl[(B \to C) \to (A \to C)\Bigr]
def prf( prfAiB: Callable[[A], B]
       , prfBiC: Callable[[B], C]
       ) -> Callable[[A], C]:
    def prfAiC(prfA: A) -> C:
        prfB: B = prfAiB(A)
        prfC: C = prfBiC(B)
        return prfC
    return prfAiC

Bonus slide

\neg A := A \to \mathrm{False} \qquad A \to \neg A
Void = Callable[[], T]
Not = Callable[[T], Void]

def void() -> T:
    return void()

def notA(prfA: T) -> Not[T]:
    def prf(prfA: T) -> Void:
        return void
    return prf

Примеры

  • Coq (задача о 4 красках)
  • Agda
  • Lean
  • KeY (TimSort bug, 2015 год)
  • K Framework

Литература

  1. Abelson, H., Sussman, G. and Sussman, J. (2010). Structure and interpretation of computer programs. Cambridge, Mass: MIT Press.
  2. Pierce, B. (2002). Types and programming languages. Cambridge, Mass.: MIT Press.
  3. Python.org. (2018). PEP 483 -- The Theory of Type Hints. [online] Available at: https://www.python.org/dev/peps/pep-0483/
  4. Python.org. (2018). PEP 484 -- Type Hints. [online] Available at: https://www.python.org/dev/peps/pep-0484/
  5. Python.org. (2018). PEP 557 -- Data Classes. [online] Available at: https://www.python.org/dev/peps/pep-0557/
  6. Meyer, C. (2018). Type-checked Python in the real world - PyCon 2018. [online] YouTube. Available at: https://www.youtube.com/watch?v=pMgmKJyWKn8

Литература

  1. Siek, J. (2018). What is Gradual Typing. [online]      Wphomes.soic.indiana.edu. Available at: https://wphomes.soic.indiana.edu/jsiek/what-is-gradual-typing/
  2. Existential Type. (2018). Dynamic Languages are Static Languages. [online] Available at: https://existentialtype.wordpress.com/2011/03/19/dynamic-languages-are-static-languages/
  3. Wadler, P. (2018). Propositions as Types. [online] YouTube. Available at: https://www.youtube.com/watch?v=IOiZatlZtGU
  4. Milewski, B. (2018). A Crash Course in Category Theory. [online] YouTube. Available at: https://www.youtube.com/watch?v=JH_Ou17_zyU

Литература

  1. Chaudhuri, A., Vekris, P., Goldman, S., Roch, M. and Levi, G. (2017). Fast and precise type checking for JavaScript. Proceedings of the ACM on Programming Languages, 1(OOPSLA), pp.1-30.
  2. Брагилевский, В. (2017). Программирование с зависимыми типами на языке Idris, весна 2017. [online] Compsciclub.ru. Available at: https://compsciclub.ru/courses/idrisprogramming/2017-spring/
  3. Wadler, P. (2015). Propositions as types. Communications of the ACM, 58(12), pp.75-84.

Вопросы

Спасибо за внимание!

Python typing

By Maxim Koltsov

Python typing

  • 2,341