Введение

Стиль компьютерного программирования

Обобщённое программирование — это стиль компьютерного программирования, в котором алгоритмы записываются в терминах типов данных, которые будут определены позднее и затем инстанцируются при необходимости для конкретных типов, предоставляемых в качестве параметров. Этот подход, впервые реализованный в языке ML в 1973 году, позволяет создавать универсальные функции или типы, которые различаются только набором типов, над которыми они работают при использовании, тем самым уменьшая дублирование кода. Обобщённое программирование вошло в широкое употребление с языком Ada в 1977 году. С появлением шаблонов в C++ обобщённое программирование стало частью арсенала профессионального проектирования библиотек. Методы были усовершенствованы, а параметризованные типы были введены во влиятельной книге 1994 года «Design Patterns». Новые методы были представлены Андреем Александреску в его книге 2001 года «Современный дизайн C++: обобщённое программирование и шаблоны проектирования». Впоследствии язык D реализовал те же идеи. Такие программные сущности известны как дженерики в Ada, C#, Delphi, Eiffel, F#, Java, Nim, Python, Go, Rust, Swift, TypeScript и Visual Basic NET. Они известны как параметрический полиморфизм в ML, Scala, Julia и Haskell. (Терминология Haskell также использует термин «обобщённый» для связанного, но несколько отличного понятия.) Термин «обобщённое программирование» был первоначально предложен Дэвидом Муссером и Александром Степановым в более узком смысле, чем выше, для описания парадигмы программирования, в которой фундаментальные требования к типам данных абстрагируются из конкретных примеров алгоритмов и структур данных и формализуются в виде концепций, а обобщённые функции реализуются в терминах этих концепций, как правило, с использованием механизмов языковой обобщённости, как описано выше.

Поддержка языка программирования для обобщения

Механизмы обобщения существуют в языках высокого уровня, по крайней мере, с 1970-х годов, в таких языках, как ML, CLU и Ada, и впоследствии были приняты многими языками, основанными на объектной и объектно-ориентированной парадигмах, включая BETA, C++, D, Eiffel, Java и устаревшую Trellis Owl от DEC. Реализация и поддержка обобщения различаются в разных языках программирования; термин "обобщенный" также используется по-разному в различных контекстах программирования. Например, в Forth компилятор может выполнять код во время компиляции, и можно создавать новые ключевые слова компилятора и новые реализации для этих слов "на лету". В нем немного средств для раскрытия поведения компилятора, и поэтому он естественным образом предоставляет возможности обобщения, которые, однако, обычно не называют таковыми в большинстве текстов по Forth. Аналогично, динамически типизированные языки, особенно интерпретируемые, обычно предлагают обобщение по умолчанию, поскольку передача значений функциям и присваивание значений не зависят от типа, и такое поведение часто используется для абстракции или лаконичности кода, однако это обычно не квалифицируется как обобщение, поскольку является прямым следствием используемой в языке динамической системы типизации. Этот термин также использовался в функциональном программировании, в частности, в языках, подобных Haskell, которые используют структурную систему типов, где типы всегда параметрические, а код, работающий с этими типами, является обобщенным. Все эти подходы служат схожей цели – экономии кода и реализации абстракции. Массивы и структуры можно рассматривать как предопределенные обобщенные типы. Каждое использование типа массива или структуры создает новый конкретный тип или повторно использует ранее созданный экземпляр типа. Типы элементов массива и типы элементов структуры являются параметризованными типами, которые используются для создания экземпляра соответствующего обобщенного типа. Все это обычно встроено в компилятор, и синтаксис отличается от других обобщенных конструкций. Некоторые расширяемые языки программирования пытаются объединить встроенные и определяемые пользователем обобщенные типы. Далее следует широкий обзор механизмов обобщения в языках программирования. Для конкретного обзора, сравнивающего пригодность механизмов для обобщенного программирования, см.

Дженерики в Ада

Ada поддерживала обобщённое программирование с момента её разработки в 1977–1980 годах. Стандартная библиотека использует обобщённые типы для предоставления множества возможностей. Ada 2005 добавляет в стандартную библиотеку комплексную библиотеку обобщённых контейнеров, вдохновлённую стандартной библиотекой шаблонов C++. Обобщённый модуль – это пакет или подпрограмма, принимающая один или несколько обобщённых формальных параметров. Обобщённый формальный параметр может быть значением, переменной, константой, типом, подпрограммой или даже экземпляром другого, указанного, обобщённого модуля. Для обобщённых формальных типов синтаксис различает дискретные, с плавающей точкой, с фиксированной точкой, типы доступа (указатели) и другие. Некоторые формальные параметры могут иметь значения по умолчанию. Для создания экземпляра обобщённого модуля программист передаёт фактические параметры для каждого формального параметра. Полученный обобщённый экземпляр ведёт себя как любой другой модуль. Возможно создание экземпляров обобщённых модулей во время выполнения, например, внутри цикла.

Шаблоны на C++

C++ использует шаблоны для реализации техник обобщенного программирования. Стандартная библиотека C++ включает в себя Стандартную библиотеку шаблонов (STL), предоставляющую основу из шаблонов для распространенных структур данных и алгоритмов. Шаблоны в C++ также могут применяться для метапрограммирования шаблонов, представляющего собой способ предварительной оценки части кода на этапе компиляции, а не во время выполнения. Благодаря специализации шаблонов, шаблоны C++ являются Тьюринг-полными.

Специализация шаблонов

Мощная особенность шаблонов C++ — специализация шаблонов. Она позволяет предоставлять альтернативные реализации в зависимости от определенных характеристик параметризованного типа, для которого производится инстанцирование. Специализация шаблонов преследует две цели: обеспечение определенных видов оптимизации и уменьшение раздувания кода. Например, рассмотрим шаблонную функцию сортировки. Одной из основных задач такой функции является обмен значениями в двух позициях контейнера. Если значения велики (с точки зрения объема памяти, необходимого для хранения каждого из них), то часто быстрее сначала создать отдельный список указателей на объекты, отсортировать эти указатели, а затем построить окончательную отсортированную последовательность. Однако, если значения достаточно малы, обычно быстрее всего просто обменивать значения непосредственно по мере необходимости. Более того, если параметризованный тип уже является типом указателя, то нет необходимости создавать отдельный массив указателей. Специализация шаблонов позволяет автору шаблона создавать различные реализации и указывать характеристики, которым должен соответствовать параметризованный тип (или типы) для использования каждой реализации. В отличие от шаблонов функций, шаблоны классов могут быть частично специализированы. Это означает, что альтернативная версия кода шаблона класса может быть предоставлена, когда некоторые параметры шаблона известны, а остальные параметры остаются обобщенными. Это можно использовать, например, для создания реализации по умолчанию (основной специализации), которая предполагает, что копирование параметризующего типа является дорогостоящей операцией, а затем создать частичные специализации для типов, копирование которых дешево, тем самым повышая общую эффективность. Клиенты такого шаблона класса просто используют его специализации, не зная, использовал ли компилятор основную специализацию или какую-либо частичную специализацию в каждом конкретном случае. Шаблоны классов также могут быть полностью специализированы, что означает, что альтернативная реализация может быть предоставлена, когда известны все типы параметризации.

Общественность в Эйфеле

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

Генерики на Яве

Поддержка генериков, или "контейнеров типа T", была добавлена в язык программирования Java в 2004 году в составе J2SE 5.0. В Java генерики проверяются на корректность типов только во время компиляции. Затем информация о генерическом типе удаляется посредством процесса, называемого стиранием типов, для обеспечения совместимости со старыми реализациями JVM, что делает её недоступной во время выполнения. Например, List<String> преобразуется к исходному типу List. Компилятор добавляет приведения типов для преобразования элементов к типу String при их извлечении из списка, что снижает производительность по сравнению с другими реализациями, такими как шаблоны C++.