Зарегистрируйтесь, чтобы продолжить обучение

Параметрический полиморфизм Python: Полиморфизм

Полиморфизм играет важную роль в программировании. Он обеспечивает универсальность и повторное использование кода, что упрощает разработку и поддержку программного обеспечения. Также он позволяет объектам и функциям работать с данными разных типов.

В языках программирования существуют разные виды полиморфизма. В императивных языках, например, Java и C++, есть полиморфизм подтипов — интерфейсы, и параметрический полиморфизм — дженерики.

В функциональных языках, например, Haskell и Lisp, существует параметрический полиморфизм и высший-родовой полиморфизм — функторы, монады. Эти виды полиморфизма позволяют создавать гибкие и масштабируемые структуры данных и функции.

Применение параметрического полиморфизма

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

Получается, что одна и та же функция или структура данных может быть использована с различными типами данных. Это делает код более универсальным и повторно используемым.

Параметрический полиморфизм имеет множество преимуществ. Одно из них — увеличение повторного использования кода. Вместо написания функции для каждого типа данных мы можем написать одну функцию, которая работает с любым типом данных. Это также делает код более универсальным и масштабируемым.

Рассмотрим, как это работает на примерах из языков с динамической и статической типизацией.

Параметрический полиморфизм в языках с динамической типизацией

Рассмотрим следующий пример Python кода:

from itertools import chain

list(chain([1], [2, 3, 1]))  # [1, 2, 3, 1]
list(chain({"one"}, {"two", "three"}))  # ['one', 'two', 'three']
list(
    chain(
        (True,),
        (
            False,
            False,
            True,
        ),
    )
)  # [True, False, False, True]

Здесь используется функция chain из модуля itertools, которая позволяет объединять несколько итерируемых объектов в один итератор. Она принимает произвольное количество аргументов и возвращает один итератор, который состоит из всех элементов, переданных итерируемых объектов.

Также дополнительно используется явное преобразование в list, потому что chain возвращает собственный тип объекта — itertools.chain объект.

Теперь реализуем данную функцию:

def my_chain(*iterables):
    result = []
    for iterable in iterables:
        for item in iterable:
            result.append(item)
    return result

В примере функция my_chain принимает произвольное количество итерируемых объектов и объединяет их элементы в один список. Внутри функции используются два вложенных цикла for для прохода по всем переданным итерируемым объектам и их элементам. Затем каждый элемент добавляется в результирующий список result.

Если рассмотреть код функции my_chain, можно заметить, что никакие операции над данными внутри коллекций не выполняются. Данные перекладываются из одной коллекции в другую, но не изменяются.

Новая функция my_chain(), как и стандартная функция chain() из модуля itertools, может работать с коллекциями, которые содержат любые типы данных.

Для разработчиков, которые всегда писали на языках с динамической типизацией, это кажется естественным. В языках со статической типизацией все не так.

Параметрический полиморфизм в языках со статической типизацией

Ниже пример определения массивов в Java:

int numbers[] = {3, 1, 2, 5, 4};
String words[] = {"one", "two", "three"};

Для массивов нужно указывать тип данных, которые ожидаются внутри. Для первого массива — это int, для второго — String. Нельзя создать массив и не указать тип его значений. То же самое касается функций, которые обрабатывают массивы:

class Main {
    public static void main(String[] args) {
        // Объявление массива a
        int[] a = {1, 2, 3, 4};
        // Объявление массива b
        int[] b = {4, 16, 1, 2, 3, 22};

        // Сливаем массивы
        chain_(a, b);
    }

    // На вход могут приходить массивы, содержащие только int
    public static int[] chain_(int[] arr1, int[] arr2) {
        // Создаем результирующий массив, длина которого равна сумме длин исходных массивов
        int[] result = new int[arr1.length + arr2.length];

        // Переносим в result все значения из первого массива
        for (int i = 0; i < arr1.length; i++) {
           result[i] = arr1[i];
        }

        // Переносим в result все значения из второго массива
        for (int j = 0; j < arr2.length; j++) {
           result[arr1.length + j] = arr2[j];
        }

        return result;
    }
}

Обратите внимание на сигнатуру метода int[] chain_(int[] arr1, int[] arr2). В отличие от варианта на Python здесь указано, что входными параметрами являются массивы чисел. То есть для массива строк эта функция работать не будет. Не будет она работать и для остальных типов данных.

Это означает, что нам придется реализовывать подобную функцию для каждого типа при том, что алгоритм внутри идентичен.

Здесь нам пригодится параметрический полиморфизм.

Статическим языкам приходится вводить в язык специальные конструкции, которые позволяют описывать подобные алгоритмы безотносительно типа параметра. В некоторых языках их называют шаблонами или дженериками:

class Main {
  public static void main(String[] args) {
    Integer[] a = {1, 2, 3, 4};
    Integer[] b = {4, 16, 1, 2, 3, 22};
    chain_(a, b);
  }

  public static<T> T[] chain_(T[] arr1, T[] arr2) {
      T[] result = (T[]) new Object[arr1.length + arr2.length];

      for (int i = 0; i < arr1.length; i++) {
         result[i] = arr1[i];
      }

      for (int j = 0; j < arr2.length; j++) {
         result[arr1.length + j] = arr2[j];
      }

      return result;
  }
}

В этом примере метод chain_ использует параметрический полиморфизм для работы с массивами любого типа. <T> — параметр типа, который может быть заменен любым типом класса при вызове метода chain_.

Благодаря параметрическому полиморфизму мы можем использовать одну и ту же функцию для работы с массивами разных типов данных.

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

Это упрощает разработку и обслуживание кода, поскольку мы можем использовать одну и ту же функцию для различных типов данных. При этом мы избегаем дублирование кода и улучшаем повторное использование. Также это делает код более гибким и масштабируемым, так как мы можем легко изменять типы данных, с которыми работает функция. При этом не нужно изменять саму функцию.

Параметрический полиморфизм в статически типизированных языках программирования позволяет создавать более гибкий и переиспользуемый код.

Выводы

Параметрический полиморфизм дает возможность писать алгоритмы, которые идентично обрабатывают данные разных типов. Это уменьшает количество кода и вероятность его поломки. Иногда за это приходится платить сложностью решения. Но для большинства типичных операций сложность растет не сильно. Это видно и по коду выше.

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

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff