$30 off During Our Annual Pro Sale. View Details »

Polymorphism and Typeclasses

Polymorphism and Typeclasses

Sobolev Nikita

November 14, 2021
Tweet

More Decks by Sobolev Nikita

Other Decks in Programming

Transcript

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

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

  3. None
  4. Зачем?

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

  6. None
  7. ООП Полиморфизм

  8. None
  9. parametric from typing import TypeVar X = TypeVar('X') def identity(instance:

    X) -> X: return instance
  10. 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()])
  11. ООП Полиморфизм

  12. 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
  13. coercion >>> 5 + 4.5 9.5

  14. None
  15. >_ X Я всю презентацию пока посмотрел и тайплклассов не

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

    по разному
  17. Например

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

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

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

    логика
  21. @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)), )
  22. Но как её расширить?

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

  24. @dataclass class MyUser(object): name: str def greet(self) -> str: return

    'Hello again, {0}'.format(self.name)
  25. from typing_extensions import Protocol class CanGreet(Protocol): def greet(self) -> str:

    ... def greet(instance: CanGreet) -> str: return instance.greet()
  26. Минусы

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

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

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

    Объекты обрастают слишком большим числом методов > Нет возможности расширить "чужой" код
  30. Вариант 1.5: объекты + monkeypatching

  31. @dataclass class MyUser(object): name: str # Somewhere else: MyUser.greet =

    lambda self: 'Hi, {0}'.format(self.name) print(MyUser('a').greet())
  32. None
  33. None
  34. Вариант 2: дополнительные абстракции

  35. 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
  36. 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)
  37. None
  38. Минусы

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

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

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

    > Изменение АПИ
  42. Может быть все-таки тайпклассы?

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

  44. None
  45. К чему будем стремиться?

  46. @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
  47. @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
  48. @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
  49. Подозрительно похоже на singledispatch 🤔

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

  51. Питонисты 🤦

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

  53. None
  54. @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
  55. @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
  56. @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
  57. @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
  58. IO.puts(Greet.greet("world")) # Hello, world! IO.puts(Greet.greet(%MyUser{name: "example"})) # Hello again, example

  59. Минусы

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

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

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

    Нет возможности выразить "Supports[SomeTypeclass]"
  63. None
  64. 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); } }
  65. 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); } }
  66. 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); } }
  67. 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); } }
  68. 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 }
  69. Минусы

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

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

    "метода", а не "функции"
  72. None
  73. -- 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)
  74. -- 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)
  75. -- 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)
  76. -- 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)
  77. greetAlias :: Greet instance => instance -> String greetAlias =

    greet main = do print $ greetAlias "world" -- Hello, world! print $ greetAlias MyUser { name="example" } -- Hello again, example
  78. Минусы

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

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

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

  82. 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)
  83. + mypy plugin

  84. None
  85. 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]"
  86. Полезные ссылки > https://medium.com/devschacht/ polymorphism-207d9f9cd78 > https://sobolevn.me/2021/06/ typeclasses-in-python > https://classes.readthedocs.io/en/

    latest/
  87. t.me/ opensource_findings 71

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