Добровольная типизация в 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
- VS Code: mypy plugin
- PyCharm: mypy plugins
- Language Server: python-language-server + pyls-mypy
- vim
- VS Code
- emacs
- PyCharm
- MonkeyType
- + добавьте в ваш CI
Сложные системы типов
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
Типы как логика
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)
Простое доказательство
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
Простое доказательство
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
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
Литература
- Abelson, H., Sussman, G. and Sussman, J. (2010). Structure and interpretation of computer programs. Cambridge, Mass: MIT Press.
- Pierce, B. (2002). Types and programming languages. Cambridge, Mass.: MIT Press.
- Python.org. (2018). PEP 483 -- The Theory of Type Hints. [online] Available at: https://www.python.org/dev/peps/pep-0483/
- Python.org. (2018). PEP 484 -- Type Hints. [online] Available at: https://www.python.org/dev/peps/pep-0484/
- Python.org. (2018). PEP 557 -- Data Classes. [online] Available at: https://www.python.org/dev/peps/pep-0557/
- Meyer, C. (2018). Type-checked Python in the real world - PyCon 2018. [online] YouTube. Available at: https://www.youtube.com/watch?v=pMgmKJyWKn8
Литература
- Siek, J. (2018). What is Gradual Typing. [online] Wphomes.soic.indiana.edu. Available at: https://wphomes.soic.indiana.edu/jsiek/what-is-gradual-typing/
- Existential Type. (2018). Dynamic Languages are Static Languages. [online] Available at: https://existentialtype.wordpress.com/2011/03/19/dynamic-languages-are-static-languages/
- Wadler, P. (2018). Propositions as Types. [online] YouTube. Available at: https://www.youtube.com/watch?v=IOiZatlZtGU
- Milewski, B. (2018). A Crash Course in Category Theory. [online] YouTube. Available at: https://www.youtube.com/watch?v=JH_Ou17_zyU
Литература
- 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.
- Брагилевский, В. (2017). Программирование с зависимыми типами на языке Idris, весна 2017. [online] Compsciclub.ru. Available at: https://compsciclub.ru/courses/idrisprogramming/2017-spring/
- Wadler, P. (2015). Propositions as types. Communications of the ACM, 58(12), pp.75-84.
Вопросы
Спасибо за внимание!
Python typing
By Maxim Koltsov
Python typing
- 2,571