- Применение параметрического полиморфизма
- Параметрический полиморфизм в языках с динамической типизацией
- Параметрический полиморфизм в языках со статической типизацией
- Выводы
Полиморфизм играет важную роль в программировании. Он обеспечивает универсальность и повторное использование кода, что упрощает разработку и поддержку программного обеспечения. Также он позволяет объектам и функциям работать с данными разных типов.
В языках программирования существуют разные виды полиморфизма. В императивных языках, например, 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_
.
Благодаря параметрическому полиморфизму мы можем использовать одну и ту же функцию для работы с массивами разных типов данных.
Параметрический полиморфизм в языках со статической типизацией позволяет создавать универсальные алгоритмы и функции, которые могут работать с различными типами данных. Вместо того чтобы создавать отдельные версии функции для каждого типа, мы можем использовать одну обобщенную функцию, которая принимает параметр типа и может быть применена к разным типам данных.
Это упрощает разработку и обслуживание кода, поскольку мы можем использовать одну и ту же функцию для различных типов данных. При этом мы избегаем дублирование кода и улучшаем повторное использование. Также это делает код более гибким и масштабируемым, так как мы можем легко изменять типы данных, с которыми работает функция. При этом не нужно изменять саму функцию.
Параметрический полиморфизм в статически типизированных языках программирования позволяет создавать более гибкий и переиспользуемый код.
Выводы
Параметрический полиморфизм дает возможность писать алгоритмы, которые идентично обрабатывают данные разных типов. Это уменьшает количество кода и вероятность его поломки. Иногда за это приходится платить сложностью решения. Но для большинства типичных операций сложность растет не сильно. Это видно и по коду выше.
В динамических языках для реализации обобщенных алгоритмов параметрический полиморфизм не нужен. Коллекция может содержать любые типы данных в любой момент времени. Благодаря этому не требуется вводить дополнительные языковые конструкции и изучать новые концепции.
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.