Slide 1

Slide 1 text

Никита Соболев github.com/sobolevn

Slide 2

Slide 2 text

>_ X Тайпклассы или "откуда в полиморфизме дырки"

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Зачем?

Slide 5

Slide 5 text

>_ X Поли- что?!

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

ООП Полиморфизм

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

parametric from typing import TypeVar X = TypeVar('X') def identity(instance: X) -> X: return instance

Slide 10

Slide 10 text

inclusion / subtyping class Animal(object): def say(self) -> None: print('** wild animal noises **') class Dog(Animal): def say(self) -> None: print('woof') def say_all(animals: list[Animal]) -> None: for animal in animals: print(animal.say()) say_all([Animal(), Dog()])

Slide 11

Slide 11 text

ООП Полиморфизм

Slide 12

Slide 12 text

overloading from typing import overload @overload def sum_two(a: str, b: str) -> str: ... @overload def sum_two(a: int, b: int) -> int: ... def sum_two(a, b) -> str | int: return a + b

Slide 13

Slide 13 text

coercion >>> 5 + 4.5 9.5

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

>_ X Я всю презентацию пока посмотрел и тайплклассов не увидел! Где?!

Slide 16

Slide 16 text

Проблема: Некоторые функции просто рождены работать с разными типами данными по разному

Slide 17

Slide 17 text

Например

Slide 18

Slide 18 text

Например > Сериализация и десереализация данных

Slide 19

Slide 19 text

Например > Сериализация и десереализация данных > Композиция

Slide 20

Slide 20 text

Например > Сериализация и десереализация данных > Композиция > Бизнес логика

Slide 21

Slide 21 text

@dataclass class MyUser(object): name: str def greet(instance: str | MyUser) -> str: if isinstance(instance, str): return 'Hello, "{0}"!'.format(instance) elif isinstance(instance, MyUser): return 'Hello again, {0}'.format(instance.name) raise NotImplementedError( 'Cannot greet "{0}" type'.format(type(instance)), )

Slide 22

Slide 22 text

Но как её расширить?

Slide 23

Slide 23 text

Вариант 1: использовать объекты и методы

Slide 24

Slide 24 text

@dataclass class MyUser(object): name: str def greet(self) -> str: return 'Hello again, {0}'.format(self.name)

Slide 25

Slide 25 text

from typing_extensions import Protocol class CanGreet(Protocol): def greet(self) -> str: ... def greet(instance: CanGreet) -> str: return instance.greet()

Slide 26

Slide 26 text

Минусы

Slide 27

Slide 27 text

Минусы > Оно точно не сломает нашу объектную модель?

Slide 28

Slide 28 text

Минусы > Оно точно не сломает нашу объектную модель? > Объекты обрастают слишком большим числом методов

Slide 29

Slide 29 text

Минусы > Оно точно не сломает нашу объектную модель? > Объекты обрастают слишком большим числом методов > Нет возможности расширить "чужой" код

Slide 30

Slide 30 text

Вариант 1.5: объекты + monkeypatching

Slide 31

Slide 31 text

@dataclass class MyUser(object): name: str # Somewhere else: MyUser.greet = lambda self: 'Hi, {0}'.format(self.name) print(MyUser('a').greet())

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

Вариант 2: дополнительные абстракции

Slide 35

Slide 35 text

import abc from typing import Generic, TypeVar _Wrapped = TypeVar('_Wrapped') class BaseGreet(Generic[_Wrapped]): """Abstract class of all other """ def __init__(self, wrapped: _Wrapped) -> None: self._wrapped = wrapped @abc.abstractmethod def greet(self) -> str: raise NotImplementedError

Slide 36

Slide 36 text

class StrGreet(BaseGreet[str]): def greet(self) -> str: return 'Hello, {0}!'.format(self._wrapped) @dataclass class MyUser(object): name: str class MyUserGreet(BaseGreet[MyUser]): def greet(self) -> str: return 'Hello again, {0}'.format(self._wrapped.name)

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

Минусы

Slide 39

Slide 39 text

Минусы > Очень много лишних сущностей

Slide 40

Slide 40 text

Минусы > Очень много лишних сущностей > "Разрывы" в логике

Slide 41

Slide 41 text

Минусы > Очень много лишних сущностей > "Разрывы" в логике > Изменение АПИ

Slide 42

Slide 42 text

Может быть все-таки тайпклассы?

Slide 43

Slide 43 text

Тайпклассы – пример ad-hoc полиморфизма

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

К чему будем стремиться?

Slide 46

Slide 46 text

@typeclass def greet(instance) -> str: raise NotImplementedError( 'Cannot greet "{0}" type'.format(type(instance)), ) @greet.instance(str) def _greet_str(instance: str) -> str: return 'Hello, "{0}"!'.format(instance) @greet.instance(MyUser) def _greet_myuser(instance: MyUser) -> str: return 'Hello again, {0}'.format(instance.name) greet('world') # => Hello, "world"! greet(MyUser('example')) # => Hello again, example

Slide 47

Slide 47 text

@typeclass def greet(instance) -> str: raise NotImplementedError( 'Cannot greet "{0}" type'.format(type(instance)), ) @greet.instance(str) def _greet_str(instance: str) -> str: return 'Hello, "{0}"!'.format(instance) @greet.instance(MyUser) def _greet_myuser(instance: MyUser) -> str: return 'Hello again, {0}'.format(instance.name) greet('world') # => Hello, "world"! greet(MyUser('example')) # => Hello again, example

Slide 48

Slide 48 text

@typeclass def greet(instance) -> str: raise NotImplementedError( 'Cannot greet "{0}" type'.format(type(instance)), ) @greet.instance(str) def _greet_str(instance: str) -> str: return 'Hello, "{0}"!'.format(instance) @greet.instance(MyUser) def _greet_myuser(instance: MyUser) -> str: return 'Hello again, {0}'.format(instance.name) greet('world') # => Hello, "world"! greet(MyUser('example')) # => Hello again, example

Slide 49

Slide 49 text

Подозрительно похоже на singledispatch 🤔

Slide 50

Slide 50 text

def proxy_greet(instance: Supports[greet]) -> str: return greet(instance)

Slide 51

Slide 51 text

Питонисты 🤦

Slide 52

Slide 52 text

>_ X Примеры реализаций

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

@doc "Our custom protocol" defprotocol Greet do # This is an abstract function, # that will behave differently for each type. def greet(data) end @doc "Enhancing built-in type" defimpl Greet, for: BitString do def greet(string), do: "Hello, #{string}!" end @doc "Custom data type" defmodule MyUser do defstruct [:name] end @doc "Enhancing our own type" defimpl Greet, for: MyUser do def greet(user), do: "Hello again, #{user.name}" end

Slide 55

Slide 55 text

@doc "Our custom protocol" defprotocol Greet do # This is an abstract function, # that will behave differently for each type. def greet(data) end @doc "Enhancing built-in type" defimpl Greet, for: BitString do def greet(string), do: "Hello, #{string}!" end @doc "Custom data type" defmodule MyUser do defstruct [:name] end @doc "Enhancing our own type" defimpl Greet, for: MyUser do def greet(user), do: "Hello again, #{user.name}" end

Slide 56

Slide 56 text

@doc "Our custom protocol" defprotocol Greet do # This is an abstract function, # that will behave differently for each type. def greet(data) end @doc "Enhancing built-in type" defimpl Greet, for: BitString do def greet(string), do: "Hello, #{string}!" end @doc "Custom data type" defmodule MyUser do defstruct [:name] end @doc "Enhancing our own type" defimpl Greet, for: MyUser do def greet(user), do: "Hello again, #{user.name}" end

Slide 57

Slide 57 text

@doc "Our custom protocol" defprotocol Greet do # This is an abstract function, # that will behave differently for each type. def greet(data) end @doc "Enhancing built-in type" defimpl Greet, for: BitString do def greet(string), do: "Hello, #{string}!" end @doc "Custom data type" defmodule MyUser do defstruct [:name] end @doc "Enhancing our own type" defimpl Greet, for: MyUser do def greet(user), do: "Hello again, #{user.name}" end

Slide 58

Slide 58 text

IO.puts(Greet.greet("world")) # Hello, world! IO.puts(Greet.greet(%MyUser{name: "example"})) # Hello again, example

Slide 59

Slide 59 text

Минусы

Slide 60

Slide 60 text

Минусы > Не очень широкое применение

Slide 61

Slide 61 text

Минусы > Не очень широкое применение > Динамическая типизация

Slide 62

Slide 62 text

Минусы > Не очень широкое применение > Динамическая типизация > Нет возможности выразить "Supports[SomeTypeclass]"

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

trait Greet { fn greet(&self) -> String; } impl Greet for String { fn greet(&self) -> String { return format!("Hello, {}!", &self); } } struct MyUser { name: String, } impl Greet for MyUser { fn greet(&self) -> String { return format!("Hello again, {}", self.name); } }

Slide 65

Slide 65 text

trait Greet { fn greet(&self) -> String; } impl Greet for String { fn greet(&self) -> String { return format!("Hello, {}!", &self); } } struct MyUser { name: String, } impl Greet for MyUser { fn greet(&self) -> String { return format!("Hello again, {}", self.name); } }

Slide 66

Slide 66 text

trait Greet { fn greet(&self) -> String; } impl Greet for String { fn greet(&self) -> String { return format!("Hello, {}!", &self); } } struct MyUser { name: String, } impl Greet for MyUser { fn greet(&self) -> String { return format!("Hello again, {}", self.name); } }

Slide 67

Slide 67 text

trait Greet { fn greet(&self) -> String; } impl Greet for String { fn greet(&self) -> String { return format!("Hello, {}!", &self); } } struct MyUser { name: String, } impl Greet for MyUser { fn greet(&self) -> String { return format!("Hello again, {}", self.name); } }

Slide 68

Slide 68 text

fn greet(instance: &dyn Greet) -> String { return instance.greet(); } pub fn main() { println!("{}", greet(&"world".to_string())); // Hello, world! println!("{}", greet(&MyUser { name: "example".to_string(), })); // Hello again, example }

Slide 69

Slide 69 text

Минусы

Slide 70

Slide 70 text

Минусы > Зачастую единственный способ сделать что-то

Slide 71

Slide 71 text

Минусы > Зачастую единственный способ сделать что-то > Имеет семантику "метода", а не "функции"

Slide 72

Slide 72 text

No content

Slide 73

Slide 73 text

-- Our custom typeclass class Greet instance where greet :: instance -> String -- Enhancing built-in type with it instance Greet String where greet str = "Hello, " ++ str ++ "!" -- Defining our own type data MyUser = MyUser { name :: String } -- Enhancing it instance Greet MyUser where greet user = "Hello again, " ++ (name user)

Slide 74

Slide 74 text

-- Our custom typeclass class Greet instance where greet :: instance -> String -- Enhancing built-in type with it instance Greet String where greet str = "Hello, " ++ str ++ "!" -- Defining our own type data MyUser = MyUser { name :: String } -- Enhancing it instance Greet MyUser where greet user = "Hello again, " ++ (name user)

Slide 75

Slide 75 text

-- Our custom typeclass class Greet instance where greet :: instance -> String -- Enhancing built-in type with it instance Greet String where greet str = "Hello, " ++ str ++ "!" -- Defining our own type data MyUser = MyUser { name :: String } -- Enhancing it instance Greet MyUser where greet user = "Hello again, " ++ (name user)

Slide 76

Slide 76 text

-- Our custom typeclass class Greet instance where greet :: instance -> String -- Enhancing built-in type with it instance Greet String where greet str = "Hello, " ++ str ++ "!" -- Defining our own type data MyUser = MyUser { name :: String } -- Enhancing it instance Greet MyUser where greet user = "Hello again, " ++ (name user)

Slide 77

Slide 77 text

greetAlias :: Greet instance => instance -> String greetAlias = greet main = do print $ greetAlias "world" -- Hello, world! print $ greetAlias MyUser { name="example" } -- Hello again, example

Slide 78

Slide 78 text

Минусы

Slide 79

Slide 79 text

Минусы > Ничего не понятно

Slide 80

Slide 80 text

Минусы > Ничего не понятно > Быстрее растет борода

Slide 81

Slide 81 text

>_ X Погружение в Python реализацию

Slide 82

Slide 82 text

implementation = self._exact_types.get(instance_type, None) if implementation is not None: return implementation for protocol, callback in self._protocols.items(): if isinstance(instance, protocol): return callback return _find_impl(instance_type, self._exact_types)

Slide 83

Slide 83 text

+ mypy plugin

Slide 84

Slide 84 text

No content

Slide 85

Slide 85 text

from classes import AssociatedType, Supports, typeclass class Greet(AssociatedType): """Special type to represent that some instance can `greet`.""" @typeclass(Greet) def greet(instance) -> str: """No implementation needed.""" @greet.instance(str) def _greet_str(instance: str) -> str: return 'Hello, {0}!'.format(instance) def greet_and_print(instance: Supports[Greet]) -> None: print(greet(instance)) greet_and_print('world') # ok greet_and_print(1) # Argument 1 to "greet_and_print" has incompatible type "int"; # expected "Supports[Greet]"

Slide 86

Slide 86 text

Полезные ссылки > https://medium.com/devschacht/ polymorphism-207d9f9cd78 > https://sobolevn.me/2021/06/ typeclasses-in-python > https://classes.readthedocs.io/en/ latest/

Slide 87

Slide 87 text

t.me/ opensource_findings 71

Slide 88

Slide 88 text

sobolevn.me Вопросы? github.com/sobolevn