О понимании типов ,абстракций данных и

advertisement
О понимании типов ,абстракций данных и полиморфизма.
Автор: Luca Cardelli.
Резюме
Наша цель это рассмотреть понятие типов в языках программирования,представить
модель типизированного ,полиморфного языка программирования ,который отражает
недавние исследования в теории типов и проверить значимость последних исследований
в конструировании практически применимого языка программирования.
Объектно-ориентированные языки предлагают и среду и средства для исследования
взаимодействия между представлениями типов, абстракций и полиморфизма , так как они
расширяют понятие типа до абстракции данных и так как наследование типов это важная
форма полиморфизма. Мы разработаем систему типов ,основанную на модели лямбда
исчисления , котороя позволяет нам проще исследовать эти взаимодействия , свободных
от сложностей продукционных языков программирования.
Рассматривается эволюция языков от безтиповых множеств до мономорфных и далее до
полиморфных систем типов.Проверяются механизмы для полиморфизма такие как
перегрузка ,приведение типов , выделение подтипов ,параметризация.Разрабатывается
объединенная среда для полиморфных систем типов в терминах типизированного лямбда
исчисления ,дополненного механизмом связывания типов через квантификацию.
Типовое лямбда-исчисление расширяется всеобщей квантификацией ( это кванторы
всеобщности для любого x типа A в функции F(x) ) для моделирования
шаблонных(generic) функций с параметрами-типами ( шаблонная функция определяет
абстрактную операцию , указывая имя операции и список параметров ,но не
реализацию.Она шаблонная в том смысле ,что она может принимать любые объекты в
качестве аргументов.Но сама по себе шаблонная функция не может ничего делать.Если
мы просто определим шаблонную функцию , то без разницы с какими параметрами мы её
вызовем она будет выдавать ошибку.Чтобы шаблонная функция обрабатывала
определенные аргументы мы должны определить одну или более реализаций шаблонной
функции ,называемых методами.Каждый метод представляет реализацию шаблонной
функции для определенных классов аргументов);расширяется кванторами существования
и пакетированием(сокрытие информации) для моделирования абстракных типов
данных;расширяется кванторами ограничения для моделирования выделения подтипов и
наследования типов. Итак мы получаем простое и точное описание мощной системы
типов ,котороая включает в себя абстрактные типы данных ,параметрический
полиморфизм и множественное наследование в единой непротиворечивой
среде.Обсуждаются механизмы проверки типов для расширенного лямбда
исчисления.Расширенное лямбда исчисление используется как язык программирования
для различных пояснительных примеров.Мы будем называть этот язык Fun вместо лямбда
(Fun ключевое слово для абстракции функций) так как это удобнее.Fun матиматически
прост и может служить как основа для построения и реализвции реального языка
программирования с системой типов ,которая более мощная и выразительная чем
существующие системы в других языках программирования.В частности этот язык может
служить основой для построения строго типизированного объектно-ориентированного
языка.
Содержание
1. От нетипизированных к типизированным множествам.
1.1 Организация нетипизированных множеств.
1.2 Статичное и строгое типизирование.
1.3 Типы полиморфизма.
1.4 Эволюция типов в языках прогаммирования.
1.5 Подмножество языка выражения типов.
1.6 Предварительное рассмотрение Fun.
2. Лямбда исчисление.
2.1 Безтиповое лямбда исчисление.
2.2 Типовое лямбда исчисление.
2.3 Основные типа , структурированныу типы , рекурсия.
3. Типы и множества значений.
4. Кванторы всеобности.
4.1 Кванторы всеобщности и порождающие функции.
4.2 Параметрические типы.
5. Кванторы существования.
5.1 Кванторы сущестования и сокрытие информации.
5.2 Пакетирование и абстрактные типы данных.
5.3 Совместное использование кваторов существования и всеобщности
5.4 Кванотры и модули.
5.5 Модули это значения первого класса.
6. Кванторы ограничения.
6.1 Включения типов ,поддиапозоны и наследование.
6.2 Ограниченный кваторы всеобности и выделение подтипов.
6.3 Сравнения с другими механизмами выделения подтипов.
6.4 Ограниченные кванторы существования и частичная абстракция.
7. Проверка и выводимость типов.
8. Иерархическая классификация систем типов.
9. Выводы.
1. От нетипизированных к типизированнным множествам
1.1.Организация нетипизированыых множеств
Мы не будем спрашивать Что такое тип? , вместо этого мы спросим ,для чего типы
нужны в языках программирования .Чтобы ответить на этоть вопрос мы рассмотрим , как
типы появляются в в некоторых областях компьютерных наук и математике.Путь от
нетипизированных к типизированным множествам проходили уже много раз в разных
областях и в большинстве случаев по одинаковым причинам.Рассмотрим ,например,
следующие нетипизированные множества:
(1) Битовые строки в памяти компьютера
(2) S-выражения в читсом Lisp
(3) λ-выражения в λ-исчислении
(4) Множества в теории множеств
Самое конкретное из этого всего это множество битовых строк в памяти
компьютера.”Нетипизированное” означает ,что есть только один тип и здесь
единственный тип это слово в памяти ,которое является битовой строкой фиксированного
размера.Это множество нетипизированное ,так как все без исключения ,должно быть
представлено как битовая строка: символы ,числа ,указатели ,структурированные данные
,программы , итд.Когда смотришь на кусок памяти ,то нельзя сказать ,что там
представлено.Значение или мысл этого куска памяти определяется внешней
интерпретацией его содержимого.
Лисповские S-выражения формируют другое нетипизированное множество ,которое
обычно строится на вершине предыдущего множества битовых строк.Программы и
данные не различаются и в конечном итоге все они – S-выражения.Опять мы имеем
только один тип (S-выражение) ,хотя этот тип более структурирован(различаются атомы и
последовательные ячейки) и этот имеет лучшие свойства нежели битовые строки.
В λ-исчислении все является функцией.Числа ,структуры данных и даже битовые строки
могут быть представлены подходящими функциями.Здесь тоже только один тип :
функциональный тип - из значений в значения ,где все значения сами являются
функциями тех же типов.
В теории множеств , есть элементы или множество элементов.Чтобы понять
“нетипиpованность” этого множества ,вы должны помнить ,что почти вся математика , в
которой много всяких богатых и сложных конструкций,представлена в теории множеств
множествами ,структурная сложность которых отражает сложность структур ,которое
представляются с помощью этих множеств.Например , целые числа обычно
представляются множествами множеств от множеств ,чей уровень вложенности
представляет порядок числа,функции представлены бесконечными множествами
упорядоченных пар с уникальными первыми компонентами.
Как только мы начинаем работать в нетипизированном множестве ,мы начинаем
организовывать разными способами для разных целей.Типы появляются неформально в
любой области для категоризации объектов ,на основании их использования и
поведения.Классификация объектов в терминах целей ,для которых они используются
вытекает в более-менее хорошо определенную систему типов.Типы появляются
естественно даже начиная с нетипизированного множества.В компьютерной памяти мы
различаем символы и операции ,хотя обои представлены в виде битовых строк.В Лиспе
некоторые выражения называются списками ,пока другие представляют допустимые
программы.В λ-исчислении некоторые функции выбраны для представлениябулевских
значений ,другие же представляют целые числа.В теории множеств некоторые множества
выбраны для представления упорядоченных пар ,а другие множества упорядоченных пар
представлят функции.Нетипизированное множество вычислительных объектов
разбивается на подмножества объектов с некоторым постоянным поведением.Множества
объектов с некоторым постоянным поведением могут быть названы и рассмотрены как
типы.Например ,для целых чисел постоянное поведение это то ,что к ним можно
применить одно и тоже множество операций.Для функции ,с множествами определения и
значения – целыми числами , постоянное поведение это то ,что они могут применятся к
объектам данного типа и возращать значения данного типа.
Псоле смелой попытки все организовать ,мы можем обращаться с нетипизированными
подмножествами как с типизированными.Но это только иллюзия , так как очень легко
разрушить типовую структуру ,которую мы только что создали.В компьютерной памяти
что является является результатом операции поразрядного or символа и машинной
операции.В лиспе ,что будет результатом обращения к произвольному S-выражению как к
программе?В λ-исчислении ,что будет результатом условного выражения для
небулквского значения?В теории множеств какое объединение множеств будет являтся
функцией получения последующего элемента ,а какое – для получения предыдущего
элемента.
Эти вопросы нехорошее последствие организации нетипизированных множеств не
пройдя всего пути к типизированным системам.
1.2 Статическое и строгое типизирование
Система типов имеет своей целью обойти затруднительные вопросы о представлении и
запретить ситуации ,когда такие вопросы могут возникнуть.В математике как и в
программировании типы навязывают ограничения ,которые помогают установить
правильность.Некоторые нетипизированные множества ,такие как наивная теория
множеств противоречивой и типизированные версии этой теории позволяют исключить
противоречивость.Типизированные версии теории множеств ,так же как и
типизированные языки программирования навязывают ограничения на взаимодействия
между между объектами ,которые предотвращают от нелогичных и несостоятельных
взаимодействий с другими объектами (например от взаимодействия числа и символа).
Типы могут рассматриваться как множество одежды(или защиты),которая защищает
расположенную ниже нетипизированную систему от произвольного или
неспланированного использования.Эта “одежда” предоставляет защитный слой ,который
скрывает нижележащее представление и ограчивает способы взаимодействия между
объектами.В нетипизированной системе нетипизированные объекты обнажены ,поэтому
это представление системы открыто всем.Нарушение типовой системы включает в себя
перемещение защитного слоя одежды и проделывание опрераций над ”голым”
представлением.Объекты данного типа имеют представление ,которое признает
ожидаемые свойтсва типа данных.Представление выбирается для того ,чтобы легче
выполнять ожидаемые операции над объектами данных. Например ,позиционное
представление удобно для чисел ,так как позволяет просто определить арифметические
операции.Однако существует много возможных альтернатив выбора представления
данных.Нарушение системы типов позволяет манипулировать представлениями данных
способами ,которые не определены и не запланированы ,проще говоря ,если у нас есть
типизированные объекты ,а мы нарушаем “типизированность” совершая некоторые
операции ,то результат этих операций будет разрушительный.Например использование
целого числа в качестве указателя может стать причиной случайных изменений в
программах или данных.
Чтобы предатвротить нарушение типов ,мы вобще навязываем программам структуру
статических типов.Типы ассоциируются с константами , операторами ,переменными и
функциональными символами.Системы выведения типов может быть использована для
вывода типов выражений ,где не дано вообще или дано мало явной информации о типах.В
таких языках как Паскаль и Ада типы переменных и функциональных символов
определены дополнительным объявлением и компилятор может проверить
непротиворечивость определения и использования.В таких языках как ML явные
объявления избегаются ,где это только возможно и система может вывести тип выражения
по контексту в это же время ,устанавливая непротиворечивость использования.
Языки программирования ,в которых тип каждого выражения может быть определен
статическим анализом называются статически типизированными.Статическое
типизирование это полезное свойство,но требование того ,что все переменные и
выражения связывались с типом во время компиляции иногда очень ограничительно.Это
может быть заменено более слабым требованием ,что все выражения гарантировано
имеют тип ,хотя тип может быть статически не опознан.в общем случае это может быть
сделано добавлением некоторых проверок времени исполнения.Языки,в которых все
выражения имеют совместимые типы называются строго типизированными языками.Если
язык строго типизирован ,то его компилятор может гарантировать ,что программа
,которую он принимает будет выполнена без ошибок связанных с типами.В общем случае
мы должны бороться за строгую типизацию и принимать статическую типизацию везде
,где это возможно.Заметьте ,что каждый статически типизированный язык является строго
типизированным , обратное необязательно верно.
Статическое типизирование позволяет найти несовместимость типов во время
компиляции и оно гарантирует ,что исполняемая программа не имеет несовместимости
типов.Это помогает заранее находить ошибки типов и позволяет более эфективно
работать с программой при её выполнении.Это накладывает некоторую дициплину на
программиста ,результатом которой является более структурированная и удобочитаемая
программа.Но статическое типизирование моежт привести к потере гибкости и
выразительной силы преждевременным ограничением поведения объектов
,накладываемым определенным типом.Традиционно статически типизированне системы
не допускали приемов программирования ,которые противоречивы с ранним связыванием
объектов программы с определенным типом.Например, они не допускают использование
шаблонных функций ,в которых алгоритм применим к диапозону типов.
1.3 Виды полиморфизма
Нестрого типизированные языки ,такие как Паскаль ,основаны на идее ,что функции и
процедуры и следовательно их операнды имеют имеют единственный тип.Такие языки
называются мономорфными , в том смысле чтокаждая переменная и значение может
только одного типа.Мономорфные языки программирования могут быть сопоставлены с
полиморфными языками ,в которых некоторые типы и значения могут иметь более одного
типа.Полиморфные функции это функции чьи операнды (фактические параметры) могут
иметь более одного типа Полиморфные типы это типы , операции над которыми
применимы к значениям более чем одного типа.
Straychey [67] различал неформально два главных типа полиморфизма.
Параметрический полиморфизм получается при использовании шаблонных функций (
функция выполняет свой алгоритм на некотором диапозоне типов эти типы обладают
определенной общей структурой).Перегрузка (ad-hoc polymorphism) проявляется ,когда
функция работает с несколькими разными типами ( у которых может быть ничего общего)
и может себя вести себя по-разному для каждого типа.
Наша классификация полиморфизма ,представленная на рисунке 1 улучшает Strachey ,
вводя новую форму полиморфизма ,называемый полиморфизмом включения ,для
моделирования выделения подтипов и наследования.Параметрический полиморфизм и
полиморфизм включения классифицированы как две главных подкатегории
универсального полиморфизма ,которыйпротивопоставлен неуниверсальному
полиморфизму(перегрузке).Рисунок 1 отражает точку зрения Strachey на полиморфизм ,но
еще добавляет полиморфизм включения для моделирования объектно-ориентированных
языков.Параметрический полиморфизм так называется так как единообразность структур
типов обычно достигается с помощбю параметров ,но единообразность может быть
достигнута разными способами и эта более общая концепция называется универсальным
полиморфизмом.Универсльно полиморфные фукнции будут нормально работать на
бесконечном числе типов (все типы имеет некоторую общую структуру) ,перегруженные
же функции будут работать только на конечном множестве разных потенциально
несвязных типах.В случае универсального полиморфизма можно с уверенностью заявить
,что некоторые значения ( то есть полиморфные функции) имеют много типов , когда как
в перегрузке это трудно поддерживать , а можно утверждать ,что функции перегрузки это
маленькое подмножество мономорфных функций.В терминах реализации универсальные
полиморфные функции будут выполнять один и тот же код для аргументов всех
допустимых типов ,тогда как перегруженные функции могут исполнять разный код для
каждого типа аргументов.
Есть два типа универсального полиморфизма ,то есть два способа ,в которых значение
может иметь много типов.В параметрическом полиморфизме полиморфная функция
имеет явный и неявный парметр типа ,который определяет тип аргумента для каждого
применения этой функции.В полиморфизме включения объект может быть представлен
как принадлежащий многим различным классам , которые не должны быть отдельными ,
то есть может быть вложение классов.Эти два представления универсального
полиморфизма являются связанными , но они достаточно отличаются .
Функции обладающие параметрическим полиморфизмом ,также называются
шаблонными функциямиНапрмер,функция длины (length) принимающая на вход список
произвольных типов и возвращающая целое число называется шаблонной функцией
длины.Шаблонная функция это та функция ,которая может работать с аргументами
различных типов ,в общем случае проделываю одну и ту же работу независимо от типа
аргумента.
Есть также два типа перегрузочного полиморфизма.В перегрузке одно и тоже имя
определяет несколько разных функций и какая функция определена конкретным именем
можно понять по контексту.Мы можем представить ,что препроцессор программы уберет
перегрузку ,дав различные имена различным функциям,в этом смысле перегрузка это
удобное синтаксическое сокращение.Приведение типов это наоборот семантическая
операция,в которой надо конвертировать аргумент к типу ,который ожидает функция на
входе, иначе бы такая мситуация вызвала бы ошибку.Приведения типов может
проводиться статичсеки- во время компилиции будут приводится типы аргументов и
функций или динамически – во время выполнения программы.
Различие между перегрузкой и привидением неясно в некоторых ситуациях.Это в
частности верно ,когда мы рассматриваем безтиповый язык или интерпретируемый язык.
Но даже в статических компилируемых языках могут быть непонятности между двумя
формами перегрузочного полиморфизма.Например,
Здесь перегрузочный полиморфмизм + может быть объяснен следующим способом.
- Оператор + имеет 4 прегруженные значения , один для каждой комбинации типов
аргументов.
- Оператор + имеет 2 перегруженных значения , котороые соответствуют сложению целых
и действительных чисел.Где один из аргументов это целое число ,а другой это
действительное,в этом случае целое число приводится к типу действительного.
- Оператор + определен только для сложения действительных чисел и целые аргументы
всегда будут приводится к значениям действительных чисел.В этом примере мы
рассматриваем выражения ,представляющие прегрузку и приведение и оба сразу в
зависимости от решения реализации.
Если мы представляем тип как определение (правило) поведения или использования
ассоциированных с типом значений ,тогда мономорфная система типов огрнаичивает
объект на определения только одного поведения , когда как полиморфная система типов
позволяет ассоциировать знеачения с более чем одним поведением.Мономорфные языки
очень ограниченв по своей выразительной силе ,так как они не позволяют значениям или
даже синтаксическим символам , которые определяют значения иметь различные
поведения в различных контекстах использования.Такие языки как Паскаль и Ада имеют
способы ослабления строгого мономорфизма , но полимрфизм скорее исключение нежели
правило и мы можем сказать ,что они почти мономорфные.Реальные и мнимые
исключения из мономорфной типизации в обычных языках включают в себя:
(1) Перегрузка : целые константы могут быть как типа integer так и типа real.Операции
такие как + применими аргументам как типа Integer так и типа real.
(2) Приведение типов: значения типа integer могут использоваться там же где и
значения типа real и наоборот.
(3) Выделение подтипов: элементы принадлежащие к поддиапозону типов
,принадлежат также к диапозону типов содержащий этот поддиапозон.
(4) Совметсное использование значений: nil в Паскале это константа ,которая
совместно используется всеми типами указатель.
Эти 4 примера ,которые могут все существовать в одном и том же языке это
примеры четырех радикально различных способа расширения мономорфной
системы типов.Давайте посмотрим как они подходят под различные типы
полиморфизма.
Перегрузка это чисто синтаксический способ использования одного и того же
имени для различных семантических объектов ; компилятор может разрешить
неопределенность во время компиляции и дальше работать как обычно.
Приведения типов позволяет пользователю опускать необходимые
преобразования типов.Необходимые преобразования типов должны определяться
системой , внедрены в программу и использованы компилятором для генерации
требуемых преобразований типов.Приведение типов это некоторая форма
упрощения ,которая может уменьшить размер программы и улучшить читаемость
программы ,но также оно может стать причиной незаметной и подчас опасной
ошибки.Необходимость приведений типов во времени исполнения программы
обычно определяется во время компиляции ,но такие языки как Lisp имеют
множество приведений ,которые распознаются и обрабатываются только во время
исполнения.
Выделение подтипов это случай полиморфизма включения.Идея о типе ,который
является подтипом другого типа не только из-за подиапозона упорядоченных типов
таких как integer ,но также и для других сложных структур ,таких как например тип
Тойота , который является подтипом типа Транспортное средство.Каждый объект
подтипа может быть использован в контексте супертипа в том смысле ,что каждая
Тойота это транспортное средство и она подчиняется тем же операциям каким
подчиняются все транспортные средства.
Совместное использование значений это частный случай параметрического
полиморфизма.Еам може казаться ,что символ nil был перегружен ,но это было бы
странной неокончательной перегрузкой ,так как nil это действительный элемент в
бесконечном наборе типов ,которые еще не были объявлены.Более того все
использования элемента nil ,указывают на одно и тоже значение , но это не общий
случай для перегрузки.Мы также можем подумать ,что есть различные nil для
различных типов , но все эти nil имеют одинаковое представление и могут быть
объелинены.Тот факт ,что объект имеющий много типов единообразно
представляеьтся для всех типов это характеристика параметрического
полиморфизма.
Как эти ослабленные формы типизации относятся к
полиморфизму?Универсальный полиморфизм считается правильным
полиморфизмом ,тогда как перегрузочный полиморфизм в некотором смысле
мнимый полиморфизм ,чье характерные полиморфные особенности исчезают при
близком рассмотрении.Прегрузка это неправильный полиморфизм,вместо того
чтобы значение имело много типов ,мы позволяем символу иметь много типов,но
значения определяемые этими символами имеют различные и возможно
несовместимые типы.Также приведение типов не дает правильного
полиморфизма,может казаться ,что оператор принимает значения многих типов ,но
значения должны быть приведены к некоторму представлению , прежде чем
оператор сможет их использовать. ,поэтому опрератор работате только с одним
типом.Более того выходной тип больше на зависит от входного типа ,как в случае
параметрического полиморфизма.
В противоположность к перегрузке и приведению подтипов ,выделение
подтипов это пример правильного полиморфизма: объектом подтипа можно
единообразно манипулировать так елси бы он принадлежал к супертипам.В
раелизации представление выьирается очень тщательно ,поэтому не надо
использовать приведение типов используя объект подтипа в месте ,где
используется объект супертипа.В этом смысле одинаковые объекты имею много
типов(например в Симуле член подкласса может иметь боший сегмент памяти
нежели член его суперкласса а его начальный сегмент имеет ту же структуру как и
член супрекласса).Также опреации тщательно выбираются ,чтобы они могли
единообразно представлять элементы подтипов и супертипов.
Параметрический полиморфизм это чистейшая форма полиморфизма; один и тот
же объект или функция может испльзоваться единообразно в различных
контекстах без изменений ,приведений или любых проверок времени
выполнения.Однако следует заметить ,чтоэта единообразность поведения требует
,чтобы все данные были представлены единообразно.
Четыре рассмотренных способа ослабления мономорфной типизации становятся
более мощными ,когда мы рассматриваем их в связке с операторами ,функциями и
процедурами.Давайте рассмотрим некоторые примеры.Символ + может быть
перегружен для использования в одно и тоже время в качестве суммы типов integer
,типов real ,типов string.Использование одного и того же символа для этих трех
операций отражает приблизительные похожесть алгебраической структуры ,но
нарушает требование мономорфности.Противоречивость может быть устранена
типами операндов в перегруженном операторе ,но этого может быть
недостаточно.Например ,если 2 перегружено для определения целого 2 и
действительного 2 ,то 2+2 останется неопределенным и разрешается это только
присваиванием типовой переменной.
Алгол 68 хорошо известен своей причудливой формой приведения
типов.Проблемы,которые здесь нужно решить очень схожи перегрузкой ,но в
добавление есть некоторые споследствия времени выполнения.Двумерный массив
только с одния рядом может быть преобразован в вектор ,и вектор только с одним
компонентом може быть преобразован в скаляр.Условия для представления
приведения типов ,могут быть распознаны во время выполнения ,а могут появлятся
из программных ошибок.Пример Алгола 68 показал ,что привеление типов должно
быть явным и эта точка зрения была реализована в более поздних языках.
Полиморфизм вклячения может быть обнаруженво многих языках ,в котрых
Симула 67 это самый ранний пример.Классы Симулы это определенные
пользователем классыорганизованные в просто наследовательной иерархии,где
каждый класс имеет единственный суперкласс.Объекты и процедуры Симулы
полиморфны , так как объекты подкласса могет появится там где требуется объект
суперкласса.Хотя Smalltalk это нетипизированный язык ,он тоже использует
немного полиморфизма.Недавно Лисп расширил этот стиль полиморфизма до
непосредственных суперклассов.Язык ,который представляет парадигму
параметрического полиморфизма это ML ,который изначально был построен
вокруг этого стиля типизирования.В ML вы можете написать тождественную
функцию ,которая работает с любыми типами аргументов , функцию length
,которая отображает список на целое число.Также возможно шаблонную функцию
сортировки ,которая сортирует элементы любых типов.
В конце концов мы должны упомянуть шаблонные процедуры ,которые есть в
Аде , это параметризированные шаблоны, которые должны быть означены какимлибо параметром ,перед использованием.Полиморфизм шаблонных процедур в Аде
похож на параметрический полиморфизм таких языков как ML ,но ограничен для
конкретных параметров.Эти параметры могут быть параметрами-типами ,
параметрами-процедурами или просто значениями.Шаблонные процедуры с
параметрами-типами полиморфны в том смысле ,что формальные параметры типы
могут быть различными типами для различных реализаций.Однако шаблонный
полиморфизм типов в Ада синтаксический ,так как шаблонная реализация
представлена во время компиляции с конкретными значениями типов .которые
появляются во время компиляции.Семантика шаблонных процедур это
макрорасширение приводимое в жизнь типами аргументов.Поэтому шаблонные
процедуры могут считаться как сокращения множества мономорфных
процедур.Что касается полиморфизма ,они имеют преимущество в том ,что
специальный оптимальный код может быть сгенерирован для разных типов
входов.С другой стороны в правильных полиморфных системах код генерируется
только один раз для каждой шаблонной процедуры.
1.3 Еволюция типов в языках программирования
В ранних языках программирования , вычислениеассоциировалось с числовыми
вычислениями и значения имели единственный арифметический тип.Однако в 1954
в Фортране предложили раличать типы integer и float , в частности из-за того что
использование integer для итераций и вычислений с массивами было логически
отличным от использования чисел с плавающей запятой для вычилений.
Фортран различал целочисленные переменные и с плавающей запятой по первой
букве их имени.Алгол 60 сделал это различие явным введя добавочное объявление
для Integer, real и Boolean типов.Алгол 60 был первым значительным языком
,который имел явное определения типа и соответствующие требования для
проверки типов при компиляции.Эти блочные требования позволяли не только
проверку типов ,но и проверку области действия переменной при компиляции.
Определение типа в Алгол 60 было расширено другими классами значений в
шестидесятых.Из многочисленных типизированных языков разрабатываемых в
этом периоде PL/I ,Паскаль ,Алгол 60 и Симула заслуживают внимания за их вклад
в эволюцию языков программирования.
PL/I пытался скомбинировать элементы Фортрана ,Алгола ,Кобола и Лиспа.В него
включены такие типы – типизированный массив ,записи ,указатели.Но он имеет
множество лазеек :например – не требовалось определять тип значений на который
указывает указатель , что ослабляло проверку типов при компиляции.
В Паскале есть более чистое расширение типов до массивос ,записей и указателей
и типов определенных пользователем.Однако Паскаль не определяет
эквивалентность типов ,поэтому ответ на вопрос – когда два два выражения
определяют один и тот же тип зависит от реализации.Также есть вопросы со
степенью структурированности типов.Напрмер ,Паскалевское определение типа
массив ,которое включает границы массива как часть типа ограничено в таких
процедурах,которые единообразно оперируют с массивами разной
размерности.Паскаль отсавляет лазейки в сильно типизированных определениях
,не требуя определения полного типа процедур подставляемых в качестве
параметра и позволяя независимо манипулировать полями записи.Эти неясности в
паскалевской системе типов обсуждаются в [77].
Алгол 60 имеет более строгое определение типа чем Паскаль, с хорошо
определенным определением эквивалентности типов.Определение типа расширено
включением процедур как значений прервого класса(modes). Простейшие modes
включают Int , real , char , bool , string ,bits ,bytes , format ,file , конструкторы типов
включают array , struct , proc , union , ref соостветственно для конструирования
типов массив , записей ,процедур , указателей.Алгол 68 осторожно определял
правила для преобразования типов , использования
разыменований,распроцедурирование,расширение ,укрупнение ,объединения и
низведение для преобразование значений к типам нужным для дальнейших
вычислений.Проверка типов в Алголе 68 разрешена ,но алгоритм проверки типов
настколько сложен ,что вопросы эквивалентности типов и преобразования типов
настолько сложны ,что пользователь не всегда может решить их.Эта сложность
явилась результатом сложности системы типов.Поэтому в поздних языках таких
как Ада есть более простое определение эквивалентности типов с неколько
ограниченным преобразованием типов.
Симула это первый объектно-ориентированный язык.Определение типа включает
классы,чьим экземплярам можно присвоить классовые переменные и могли
существовать междк исполнением содержащихся в них процедур .Процедуры и
данные класса определяли его интерфейс и доступны пользователю.Подклассы
наследовали объявленные сущности в интерфейсе суперкласса и могут определять
добавочные операции и данные ,которые конкретизируют поведение
класса.Экземпляры класса похожи на абстракции данных в том ,что они имели
интерфейс и состояние ,существовавшее между вызовами операций ,но недостает
механизма сокрытия информации.Последующие объектно-ориентированные языки
,такие как Smalltalk и Loops комбинировали концепцию классов наследованную от
Симулы со строгим определением сокрытия информации.
Модула-2 первый широкораспространенный язык для использования модулей как
основы структурирования.Типизированные интерфейсы определяли типы и
операции доступные из модуля;типы в интерфейсе могут скрытыми чтобы
достигнуть мезанизма абстракций данных.Интерфейс может быть определен
раздельно от реализации , чтолбы разделить задачи реализации и
декалрации.Блочная область видимости сохранявшаяся внутри модулей
прекращала свою деятельность на более высоком уровнев пользу гибкой
межмодульной взаимодействиям через списки импорта и экспорта.Интерфейсы
модулей похожи на декларации классов(кроме правил области видимости) ,но
экземпляры класса это значения первого класса ,а экземпляры модулей нет.Фаза
линковки необходима для соединения экзепмляров модулей для исполнения
программыэта фаза определяется модульными интерфейсами ,но является внешней
к языку.
ML ввел понятие параметризированного полиморфизма в языки.Типы ML могут
содержать переменные типов ,которые могут быть означены разными типами в
разных контекстах.Следовательно возможно частично определять информацию о
типах и писать программы основанные на частично определенных типах ,которые
могут быть использованы на всем диапозоне этих типов.Способ частичного
определения типов это для того ,чтобы опустить декларацию типов: более общие
типы ,которые удовлетворяют данной ситуации могут быть выведены
автоматически.
Указанная выше историческая справка дает нам основу для дальнейшего
обсуждения отношений между типами , абстракций данных и пполиморфизму в
языках программирования.Мы рассмотрим нетипизированные абстракции
данных(пакеты) в Ада , укажем на методологию ,которая требует чтобы
абстракции данных имели тип и наследование , обсудим интерпретацию
наследования как полиморфизма выделения подтипов и исследуем отношения
между полиморфизмом подтипов в Smalltalk и параметрический полиморфизм в
ML.
Ада включает в себя множество модулей , включая подпрограммы для поддержки
процедурно-ориентированного программирования,пакеты для поддержки
абстракций данных и задачи для поддержки параллельного прогрраммирования.Но
зато имеет слабое определение типа ,исключая процедуры и пакеты из множества
типизированных объектов и включая типы задач намного позже в процессе дезайна
как запоздалая мысль.Выбор в качестве эквивалентности типов эквивалентности
имен намного слабее чем , идея структурной эквивалентности использованной в
Алгол 68.Строгий запрет на явное приведение типов ослабляет возможность
предоставления полиморфных операций применимых к операндам многих типов.
Пакеты в Ада имеют интерфейсное определение – название компонетов ,которые
могут быть простыми переменными , процедурами ,исключениями и даже типами.
Они могут прятать своё локальное состояния предоставляя тип данных private в
теле пакета.Пакеты это похоже на тип запись , в качестве интерфейса
перечисляются компоненты и эти компоненты определяют интерфейс.Пакеты в
Ада отличаются от записей тем,что в записях компоненты должны быть
типизированными значениями ,тогда как компоненты пакетов могут быть
процедурами ,исключениями ,типами или другими сущностямиТак как пакеты не
имеют типа ,то они не могут быть параметрами , компонентами структур или
значениями указателей.Пакеты в Ада это второстепенные объекты ,тогда как класс
в Симуле или объекты в объектно-ориентированных языках это первостепенные
объекты.
Различие в поведении пакетов и записей в Ада избегается в объектноориентированных языках ,расширением понятия типа на процедуры и абстракции
данных.В контексте этой дискусси полезно определить объктно-ориентированные
языки как расширение процедурных языков ,которые поддерживают
типизированные абстракции данных с наследованием.Таким образом мы говорим
,что язык объектно-ориентированным ,если он удовлетворяет следующим
требованиям:
- Он поддерживает объекты и абстракции данных с интерфейсом операций и
сокрытием локальной информации.
- Объекты имеют соответствующий объектный тип.
- Типы могут наследовать аттрибуты от супертипов.
Эти требования могут быть суммированы следующим образом:
Объектно-ориентированность= абстракции данных + объектные типы +
наследование.
Полезность этого определения может быть проиллюстрирована рассмотрением
влияния каждого из этих требований на методологию.Абстракции данных сами по
себе предоставляют способ организации данных с соответствующими операциями
,что сильно отличается от традиционной методологии процедурных
языков.Реализация методологии абстракций данных была одной из главных целей
Ады и эта методология описывается в литературе по Ада [83].Однако Ада
удовлетворяет только первому из трех условий объектной-ориентированности и
интересно рассмотреть влияние типов объектов и наследования на методологию
абстракций данных[86].
Требование ,что все объекты имеют тип ,позволяет им быть первостепенными
значениями ,поэтому к ним можно обращаться как к структурам данных для
вычислений.Требование наследования разрешает определять отношения между
типами.Наследование можно рассматривать как механизм структурирования типов,
который позволяет свойствам одного или более типов быть использоваными в
определениях другого типа.Определение B наследует A может быть представлено
как механизм упрощения , который избегает переопределения аттрибутов типа А в
определении B.Однако наследование это больше чем упрощение , так как оно
влияет на структуру между совокупностью связанных типов ,которая может сильно
уменбшить сложность определения системы.Это показано на примере объектной
иерархии в Smalltalk в [83].
Иерархия объектов в Smalltalk это описание среды программирования Smalltalk.По
идее это близко к функцие apply в Лисп ,которая описывает интерпретатор языка
Лисп,но более сложнее.Она описывает совокупность более 75 связанных объектов
системы в терминах иерархии наследования.Объектные типы включают в себя
численные объекты ,структурированные объекты ,объекты ввода-вывода
итд.Объектная иерархия выносит свойства общие для всех численных объектов в
супертип Number ,а свойства присущие всем структурированным объектам – в
супертип Collection.Далее свойства общие для численных ,структурированных
объектов и объектов ввода –вывода выносятся в супертип Object.Таким образом
коллекция более 75 объектов ,которая составляет среду Smalltalk описана
относительно простой структурированной иерархией объектных типов.Упрощение
предоставляемое иерархией объектов в повторном использовании суперклассов
,чьи аттрибуты совместно используются подклассами присуща коцептуальной
расчетливости ,достигаемой за счет навязывания связанной структуры коллекции
объектных типов.
Объектная иерархия в Smalltalk также замечательна тем ,что она иилюстрирует
силу полиморфизма.Мы можем определить полиморфную функцию как функцию
применимую к значениям более чем одного типа и полимрфизм включения как
отношение между типами ,которое разрешает применение операций к объектам
разных типов связанных включением.Объекты считаются коолекциями таких
операций (аттрибутов). Это подчеркивает совместное использование операций
операторами разных типов ,как главной черты полиморфизма.
Объектная иерархия Smalltalk реализуте полиморфизм ,как было сказано выше,
заключением всех аттрибутов общих для некоторой совокупности типов в
некоторый супертип.Аттрибуты общие для численных типов заключены в
супертип Number.Аттрибуты общие для типов-структур заключены в супертип
Collection.Аттрибуты общие для всех типов заключены в супертип Object.Таким
образом полиморфизм тесно связан с понятием наследования и мы можем сказать
,что выразительная сила системы типов зависит в большей степени от
полиморфизма ,который она использует.
Чтобы закончит дискуссию об эволюции типов в языках программирования мы
исследуем механизм типов в ML[84].ML это интерактивный функциональный язык
программирования ,в котором определения типов пропущенный пользователем ,
могут быть введены через выводимость типов.Если пользователь вводит “3+4” ,то
система ответчает “7:int” , при этом считается значение выражаения и выводятся
типы операндов и всего выражения.Если пользователь вводит определение
функции “fun f x = x + 1” , то система отвечает “f:int->int”, при этом система
считает значение функции и выводит её тип “int->int”.ML поддерживает
выводимость типов не только для традиционных типов ,но также для
параметрических(полиморфных) типов ,таких как например функция длины для
списков.Если пользователь ввел “fun rec length x = if x = nil then 0 else 1 +
length(tail(x));” ,то ML выведет ,что length это функция от списка с элементами
произвольного типа ,возвращающая integer(length:’a list -> int).Если пользователь
затем вводит “length[1;2;3]” ,то система выводит ,что length имеет конкретный тип
“int list -> int” и потом применяет конкретную функциюк списку integer.
Когда мы говорим ,что параметрическая функция применима к списку
произвольного типы , мы имеем в виду , этот тип может быть конкретихирован
некоторым параметром T и поэтому конкретная функция может быть вызвана для
конкретных операндов.Есть важное различие между параметрической функцией
length для списков произвольного типа и и специальной функцией для списков
типа Int.Функцмм такие как length применимы к спискам произвольных типов ,так
как они еимеют одинаковое представление параметров , что позволляет
конкретизировать тип параметра.Это различие между параметрическими
функциями и их конкретными версиями не ясно в языках ,таких как ML, так как
параметры типов ,опускаемые пользователем ,автоматически выводятся механизмо
выведения типов.
Супертипы в объектно-ориентированных языках ,могут быть представлены как
параметрические типы ,чьи параметры опущены пользователем.Чтобы понять
схожесть супертипов и параметрических типов полезно ввести нотацию ,где
параметры супертипов должны быть явно введены при выделении из супертипа
подтипа.Мы увидим ниже ,что Fun имеет явные параметры типов как для
параметрических типов так и для супертипов ,для предоставления единой модели
как для параметрического полиморфизма ,так и для полиморфизма выделения
подтипов.
1.5 Подмножество языка выражения типов.
Когда множество типов языка программирования становится богаче и множество
определяемых типов становится бесконечным, то становится полезным определять
множество типов при помощи языка выражения типов.Множество выражений
типов современного строго типизированного языка это простой подъязык этого
языка, тем неменее он не настолько тривиален.Подъязык для выражения типов в
общем случае включает базовые типы такие как integer и Boolean и составные типы
такие как массивы ,записи и процедуры составленные и базовых типов.
Подъязык для определения типов должен быть достаточно богат , чтобы
поддерживать типы для всех значений при помощи которых мы будем профодить
вычисления и достаточно гибким ,чтобы разрешать проверку типов.Одна из целей
этой статьи исследовать компромисс между богатством возможностей и удобством
использования подъязыка для выражения типов.
Подъязык для выражения типов может быть определен контекстно-свободной
грамматкиой.Однако нас интересует не только синтаксис подъязыка ,но и его
семантика.Поэтому нас интересует какие типы определять и отношения между
выражениями типов.Самое сновное отношение между типами это отношение
эквивалентности типов.Так же нас интересуют отношения подобности между
типами ,которые слабее чем отношения эквивалентности ,напрмер включение ,
которое связано с выделением подтипов.Отношения подобности между
выражениями типов ,которые позволяют выражениям типов определять более чем
один тип или быть совместимыми со многимим типами относится к
полиморфизму.
Полезность системы типов не только в во множестве типов ,которые она может
выразить , но также в типах отношений между типами.Возможность выражать
отношения между типами включает некоторую возможность вычисления над
типами ,чтобы определить удовлетворяют ли они нужным требованием.Таие
вычисления могкт быть настолько серьезными насколько позволяют их
значения.Однако нас интересуют только простые вычислительные отношения ,
которые представляют единое поведение разделяемое совокупностью типов.
Читатель ,которого интересует обсуждение алгортмов для построения языка
выражения типов и проверки типов для таких языков как Паскаль и С
отправляются читать главу 6 [85] ,которые рассматривают проверку типов для
перегрузки , приведения типов и параметрического полиморфизма.Fun добавляет
абстрактные типы данных к множеству базовых типов и добавляет подтипы и
наслендование к формам полиморфизма ,которые поддерживаются.
1.6 Предварительное рассмотрение Fun.
Fun это язык основанный на λ-исчислении,который раширяет λ-исчисление
первого порядка , особенностями второго порядка реализованных для
моделирования полиморфизма и объектно-ориентированных языков.
Секция 2 рассматривает нетипизированное и типизированное λ-исчисление и
разрабатывает базовые элементы для подъязыка выражения типов в Fun.Fun имеет
базовые типы bool, int ,real , string , структурные типы для организации записей
,функций , рекурсивных типов.Это множство типов первого порядка используется
как основа для введения параметрических типов ,абстрактных типов данных и
наследования типов .
Секция 3 быстро обозревает теоретические модели типов связанных с
элементами Fun , особенно модели ,которые представляют тип как множество
значений.Представление типов в виде множеств позволяет определить
параметрический полиморфизм как пересечение множеств соответствующих типов
и полиморфизм наследования в терминах подмножеств соответствующих
типов.Абстракци данных могут быть также определены в терминах операций
теории множеств над соответствующими типами.
Секции 4 , 5 и 6 соответственно раширяют λ-исчислении первого порядка
квантароми всеобности для реализации параметризированных типов , кванторы
существования для реализации абстракций данных и ограниченные кванторы для
реализации наследования.Синтаксические расширения для подъязыка выражения
типов определенные этими вещами можно суммировать следующим образом.
Кванторы всеобности расширяют λ-исчисление параметризированными типами
,которые могут быть определены подстановкой некоторых параметров вместо
параметров находящихся под знаком квантора.Типы которые стоят под знаком
квантора всеобщности являются сами по себе типами первого порядка и могут
быть фактическими параметрами в такой подстановке.
Кванторы существования расширяют элементы первого порядка ,разрешая
срытое представление длядля абстрактных типов данных.Взаимодействие двух
квантификаций(всеобщности и существования) проиллюстрирована в секции 5.3
для случая стека универсально квантифицированными типами и
экзестенциональноо квантифицированным представлением сокрытия данных.
Fun поддерживает сокрытие информации не только под кванторами
существования ,но также через структуру let ,что способствует сокрытию
локальных переменных в стуктуре модуля. Сокрытие при помощи let относится к
сокрытию первого порядка, так как включает в себя локальные идентификаторы и
соответствующие им значения ,тогда как сокрытие с помощью квантора
существования относится к сокрытию второго порядка ,так как включает в себя
сокрытие реализации типа.Отношения между этими двумя формами сокрытия
проиллюстрированы в секции 5.2 ,где противопоставляется сокрытие в теле пакета
с сокрытием в private частях Ада пакета.
Ограниченная квантификация расширяет λ-исчисление первого порядка
предоставляя явные параметры подтипов.Наследование моделируется явным
параметрическим выделением подтипа из супертипа , для которых будут
исполняться операторы.В объектно-ориентированных языках каждый тип это
потенциальный супертип для последующих определенных подтипов и поэтому
должны моделировантся при помощи огрниченной квантификации.Ограниченная
квантификация предоставляет механизм для объектно-ориентированного
полиморфизма , который сильно громоздок для явного использования , но полезен
иллюстрации отношения между параметрическим и наследовательным
полиморфизмом.
Секция 7 кратко обозревает проверку типов и наследование типов для Fun.Она
дополнено приложением перечисляющее все правила вывода.
Секция 8 иерархическую классификацию объектно-ориентированной системы
типов.Fun предоставляет более общую систему типов в этой
классификации.Отношение Fun к менее общим системам соостветствующих ML ,
Galileo , Amber и систем типов других языков.
Надеемся ,что читателем будет интересно читать о Fun , так же интересно как
авторам писать об этом.
2.1 Нетипизированное λ-исчисление
Эволюция от нетипизированного до типизированного множества может быть
проиллюстрировано на примере λ-исчисления , в начале разработанного как
безтиповой нотации для исследования сущности фуккционального применения
операторов к операндам.Выражения в λ-исчислении имеют следующий синтакс(мы
будем использовать fun вместо традиционной λ ,чтобы провести соостветствие
между нотациями языков программирования)
e::=x
переменная это λ-выражение
e::=fun(x)e
e::=e(e)
абстракция e
аппликация е к е
Функции идентификации и функция successor могут быть поределены в λисчислении следующим образом.Мы используем ключевое слово value ,чтобы
определить новое имя и связать его со значением или функцией.
Value id = fun(x)x
Value succ = fun(x)x + 1
функция индентификации
функция successor для integer.
Функция идентификации может быть применена к любому λ-выражению и
возвращает всегда λ-выражение.Чтобы определить сложение над целыми в чистом
λ-исчислении мы используем представление и определим сложение таким образом
,чтобы результате сложения получалось λ-выражение ,представляющее.Функция
successor может применима только к λ-выражениям ,которые представляют целые
числа.Инфиксная нотация х+1 это упрощение функциональной нотации
+(х)(1).Здесь символы 1 и + ,должны рассматриваться как сокращения выражений
для них из чистого λ-исчисления.
Правильность целочисленного сложения не требует предположений о том ,что
происходит ,когда λ-выражение ,представляющее сложение применяется к λвыражениям ,которые не представляют целые числа.Однако если вы хотите ,чтобы
наша нотация имела хорошую проверку ошибок ,желательно определить
воздействие сложения на аргументы ,которые не являются целыми ,как ошибку.Это
достигается путем в типизированных языках программирования механизмом
проверки типов ,который предотвращает возможность операций над объектами
неправильных типов.
Проверка типов в λ-исчислении ,также как и в обычных языках
программирования ,имеет тот эффект ,что большой класс правильных λ-выражений
в нетипизированном λ-исчислении становится неправильным.Класс выражений с
неправильными типами зависит системы типов ,которую он принимает и что
нежелательно ,но может зависеть от алгоритма проверки типов.
Идея оперирования над функциями с помощью λ-выражений ,чтобы строить
другие функции , может быть проиллюстрирована использованием функции twice
,которая имеет следующую форму
value twice = fun(f)fun(y)f(f(y)) - функция twice
Применяя функцию twice к функции successor можно получит функцию ,которая
будет счистать successor от successor.
twice(succ) -> fun(y) succ(succ(y))
twice(fun(x)x+1) -> fun(fun(x)x+1)((fun(x)x+1)(y))
Обзор представленный выше иллюстрирует ,как появляются типы ,когда мы
определяем нетипизированную нотацию ,такую как λ-исчиление для реализации
определенных видов вычислений ,например целочисленной арифметики.В
следующей секции мы введем явные типы в λ-мсчисление.Результирующая
нотация похожа на функциональную нотацию в традиционных типизированных
языках программирования.
2.2 Типизированное λ-исчисление.
Типизированное λ-исчисление это λ-исчисление кроме того ,что каждая
переменная должна иметь явный тип ,когда она вводится как связная
переменная.Поэтому функция successor в типизированном λ-исчислении имеет
следующую форму.
value succ = fun (x:int) x+1
Функция twice из целых в целые имеет параметр f ,чей тип int->int и может быть
записана следующим образом.
Value twice = fun(f:int->int) fun (y:int)f(f(y))
Эта нотация определяет тип функции ,но пропускает определение
результирующего типа.Мы можем определить результирующий тип ,используя
ключевое слово returns.
Value succ= fun(x:int) (returns int)x +1
Однако тип результата может быть определен из формы тела функции х+1.Мы
будем опускать результат определения типа в интересах краткости.Механизм
выведения типов ,который позволяет получить эту информацию во время
компиляции обсуждется в более поздних секциях.
Объявления типов реализуются с помощью ключевого слова type.Далее в этой
статье ,имена типов начинаются с большой буквы ,тогда как имена функций и
типов - смаленькой.
type IntPair=Int*Int
type IntFun=Int*Int
Объявления типов определяют имена для выражения типа , но они не создают
тип.Это также можно сказать ,что мы используем структурную эквивалентность
типов вместо эквивалентсности имен: два типа эквивалентны ,когда они имеют
одну и ту же структуру,несмотря на имена.
Тот факт ,что значение v имеет тип Т пишется так v:T.
(3;4):IntPair
Succ:IntFun
Нам нужно ввести переменные путем объявления типов в форме vat:T ,так как
тип переменной может быть установлен по стрктуре присвоенной
переменной.Например , тот факти ,что IntPair имеет тип IntPair может быть
установлено тем фактом ,что (3,4) имеют тип Int*Int ,который был объявлен как
эквивалентный к Int.
value intPair = (3,4)
Однако ,если мы хотим показать тип переменной как чатсь инициализации ,то мы
можем сделать это при помощи нотации value var:T = value.
value IntPair:IntPair = (3,4)
value succ:Int -> Int = fun(x:Int) x + 1
Локальные переменные могут быть объявлены при помощи конструкции let-in ,
которая вводит новую инициализированную переменную (после let)в локальную
область видимости(после in). Значение констукции это значение жтого выражения.
let a=3 in a+1
возвращает 4
Если мы хотим определить тип ,то мы можем также написать:
let a:Int = 3 in a+1
Конструкция let-in может быть поределена в терминах базисных fun – выражений.
let a:T = M in N = (fun(a:T)N)(M)
2.3 Базовые типы ,структурные типы и рекурсия.
Типизированное λ-исчисление обычно расширяется разными базовыми типами и
структурными типами.В качестве базовых типов мы будем использовать:
Unit
Bool
Int
Real
String
простейший тип из одного элементв ()
с операцией if-then-else
c арифметическими операциями и операциями сравнения
с арифметическими операциями и операциями сравнения
конкатенация строк
Структурные типы могут быть построены из базовых типов с помощью
конструкторов типов. Конструкторы типов в нашем языке включают в себя
однонаправленное отношение (->) ,Декартово произведение (*) , записи ,
универсальный тип.Пара это элемент типа декартово произведение.
value p= 3;true : Int*Bool
Операци над парами это селекторы первого и второго элемента:
fst(p) возвращает 3
snd(p) возвращает true
Записи это неупорядоченное множество именованных переменных. Тип записи
можно определить указывая тип , ассоциированный с каждым полем. Тип запись
определяется последовательностью именнованных типов ,разделенных запятой и
заключенными в фигурные скобки.
Type ARecordType = {a:Int ; b:Bool,c:String }
Запись такого типа может быть создана инициализированием полей записи
значениями требуемых типов. Это записано как полседовательность именованных
значений разделенных запятыми и заключенные в фигурные скобки.
value r: ARecordType = {a=3;b=true;c=”asd”}
Эти поля должны быть уникальными внутри каждой записи.Единственная
операция над записями это выбор поля , определяется указанием после точки имя
поля.
r.b вызвращает true.
Так как функции это первостепенные значения , записи в общем случае могут
иметь компоненты-функции.
Type FunctionRecordType = {f1:Int -> Int : Real -> Real }
Value functionRecord = {f1=succ , f2 = sin }
Тип запись может быть определена с помощью существующих типов записей и
оператора & ,который является оператором конкатенации двух записей.
Type NewFuctionRecordType = FunctionRecordType & {f3:Bool - > Bool}
Это считается хорошим сокращением , вместо того ,чтобы писать поля f1 ,f2 ,f3
явно.Это работает только , если в записи не будет дубликатных полей.
Структура данных может быть сделана локальной и private в наборе функций
объявлением let-in.Записи с компонентами функциями в частности удобный способ
для достижения этого.В примере private переменная counter совместно
используется функциями increment и total.
value counter =
let counter = ref(0)
int {increment = fun(n:Int) count : = count + n ,
total = fun() count
}
counter.increment(3)
counter.total()
возвращает 3.
Этот пример включает сторонние эффекты , клавное пользо приватных
переменных это возможномть приватно их обновлять.Примитив ref возвращает
ссылку на объект с возможностью обновления и присвоения ограниченно работают
на таких ссылках.Эта общая форма сокрытия информации ,которая позволяет
обновлять локальное состояние статическую область видимости для ограничения
видимости.
Универсальный тип это некоторая форма неупорядоченного множества
именнованных типов , которые заключены в квадратные скобки.
Элементы этого типа могут быть Integer , Boolean или String.
value v1=[a=3]
value v2=[b=true]
value v3=[c=”asd”]
Единственная операция на универсальном типе case-выбор.Case-выражение для
универсального типа AVariantType имеет следующую форму.
Case variant of
[a=variableof type Int] действие в случае а
[b=variableof type Bool] действие в случае b
[c=variableof type String] действие в случае c
где в каждом случае воодится новая переменная и связывается с соответствующим
содержимым варианта.Например ниже приведенная функция определенному
элементу типа AVariantType возвращает строку
где вариантная переменная х может быть связана с идентификаторами abInt ,aBool,
aString в зависимости от случая.
В нетипизированном λ-исчислении возможно выражать рекурсивные операторы
и использовать их ,чтобы выражать рекурсивные функции.Однако все вычисления
выразимые в λ-исчислении должны завершаться.грубо говоря , тип обычно более
сложен чем тип её результата ,так как после некотрого числа применений функции
мы получаем базовый тип;более того у нас нет незавершенных
примитивов).Следовательно рекурсивные определения вводятся как новая
концепция примитивов.Функция факториао может быть выражена так:
Для простоты мы предпологаем ,что значения,которые могут быть рекурсивно
определены это только функции.Но в конце концов мы вводим рекурсивное
оперделение типов.Это позволяет нам ,например, определять тип целочисленных
списков без использования записей или универсальных типов.
Целочисленный список это либо пустой список ,либо соединение целого числа и
целочисленного списка.
2. Типы это множества значений
Какое понятие типа наиболее адекватно , то есть могло бы содержать полиморфиз
,абстракцию и параметризацию? В предудыщих секциях мы начали описывать
ччастную систему типов ,предоставляя неформальные правила типизирования для
лингвистических конструкций ,которые мы использовали.Этих правил достаточно
,чтобы описать систему типов на интуитивном уровне , и могут быть легко
формалтзованы как система вывода типов.
Хотя нам необязательно обсуждать семантическую теорию типов подробно ,но
может быть полезным изучит некоторые базовые правила.Эти правила в свою очередь
полезны для понимания правил типизирования ,в частности касаясь концепции
выделения подтипов ,которая будет ввелена позже.
Есть множество V всех значений , содержащее простые значения такие как integer ,
структуры данных ,например пары, записи и универсалные типы и функции.Это
законченное упорядовачение ,построенное техникой Скотта[Scott, 76], но в первом
своем приближении мы можем подумать ,что это просто большое множество всех
вычислимых типов.
Тип это множество элементов V.Не все подмножества V могут быть правильными
типами ,они должны подчинятся некоторым техническим свойствам.Подмножества V
подчиняющиеся таким свойствам называются идеалами.Поэтому все типы в языках
программирования в этом смысле идеалы , мы не дожны много заботиться о
подмножествах V ,которые не являются идеалами.
Следовательно тип это идеал , который в свою очередь является множеством
значений.Более того множество всех идеалов в V , упорядоченных при помощи
включения множеств, формируют структуру.Верхшка этой структуры это тип
Top(множество всех значений ,то есть сама V).В основании структуры – пустое
множество(одноэлементное множество).
Фраза имеет тип может интерпретироваться как членство в подходящем
множестве.Так как идеалы в V могут перекрываться ,то значения могут иметь много
типов.Множество типов некотрого данного языка программирования в общем случае
это только маленькое подмножество всех идеалов в V.Например любое подмножество
целых определяет идеал ,также это делает множество пар с первыи элементом равным
3.Это приветствуется ,так как позволяет языку иметь много систем типов в одной
среде.И вы дожны решить какие системы типов кажуться вам интересными в
контекскте определенного языка.
Конкретная система типов это совокупность идеалов V , которые обычно
определяются языком выражения типов и отображением выражений типов на
идеалы.Идеалы в этой коллекции повышены до ранга типов определенного
языка.Например , мы можем выбрать целые ,пары целых ,функции из целых в целые
как нашу систему типов.Различные языки будут иметь различные системы типов ,но
все эти системы типов могут быть построены на вершине множества V.
Мономорфные системы типов это те ,в котрых каждое значение принадлежит в
большинсвте одному типу (кроме низшего элемента в V , которые принадлжит по
определению идеала всем типам).Так как типы это множества ,то значение может
принадлежать множеству типов.Полиморфная система типов это та , в которой
большие и интересные колекции значений принадлежат многим типам.Есть езе
области почти мономорфных и почти полиморных систем , поэтому определения не
точны ,но главное это то,что базовая можель идеалов в V , может объяснить все эти
степени полиморфизма.
Так как типы это множества ,подтипы просто соответствуют подмножествам.Более
того семантическое свойтсво Т1 это подтип Т2 соответствует математическому
условию
в структуре типов. Это дает очент простую интерпретацию
поддиапозона типов и наследования , как мы увидим в последующих секциях.
В конце концов если мы возьмем в качесвте системы типов единственное множество
V ,мы будеи иметь свободную систему типов ,где все значения имеют один и тот же
тип. Следовательно мы можем выразхить типипзированные и нетипизированные
языки в одном семантическом домене и сравнить их.
Структура типов содержит много особенностей ,которые могут быть названы в
любом типипзированнном языке. В действительности они включаэт бесчисленное
множество особенностей ,так как они включают каждое подмножство целых.Цель
языка типов это дать программисту возможность именовать эти типы ,которрые
соостветствуют разным степеням поведения.Чтобы сделать это языки содержат
конструкторы типов ,включая функции-констукторы типов(например тип T1 -> T2)
Для составления функционального типа Т из типов Т1 и Т2.Эти конструкторы
позволяют бесчисленое множество интеренсых типов для конструирования из
конечного множество примитивных типов.Однако могут быть интересными и типы
,которые не могут быть используя эти конструкторы.
В оставшихся секциях этой статьи мы введем более мощные конструкторы типов
,которые позволяют нам говорить о типах соответствующих бесконечным
объединением и пересечениям типов в струтктуре типов.В чатстности , универсальная
квантификация позволит нам именовать типы , котроые являются бесконечными
пересечениями в структуре типов ,тогда как экзистенциональная квантификация
позволит именовать нам типы из бесконечных объединений структуры типов.Причина
введения универсальной и экзистенциональной квантификации важность
результирующих типов в повышении выразительной силы типизированного языка.К
счастью эти концепции также математичсеки просты и также ,что они относятся к
хорошо известным математичсеким конструкциям.
Модель идеалов это не единственная модель типов ,которая изучалась.По
сравнению с другими денотационными моделями ,она имеет преимущество в
объяснении простых и полиморфных типов интуитивным способом, терминах
множеств значений и позволяет нормально определить наследование.Однако оставляет
желать лучшего отнощение к параметризации , которая в некотром смысле не прямая ,
так как типы не могут быть значениями и отношение и типовыми операторами , что
включает в себя выход за предел модели и рассмотрения функций на идеалах. С точки
зрения этих интуитивных преимуществ,мы выбрали модель идеалов ,как базовое
представление типов , но многое из наших обсуждений должно быть доделано до
конца и иногда даже улучшено ,если мы выберем другие модели.
Идея типов в качестве параметров полно разработана в λ-исчислении второго
порядка.Денотационная модель λ-исчисления второго порядка это модель
ретрактов.Здест типы не множества объектов ,а специальные функции (называемые
ретракты);это может быть интерпретировано как определение множеств объектов.Так
как своство этих типов это объекты ,модель ретрактов может еще лучше описывать
явные пипизированные параметры, тогда как идеалы могут описывать лучше ниявные
типипзированные параметры.
4.Универсальная квантификация.
4.1.Универсальная квантификация и шаблоннные функции.
Типизированного λ-исчисления достаточно ,чтобы выражать мономорфные
функции.Однако это надекуватная модель для полиморных функций.Например
,требуется ,чтобы функция twice необязательно была ограничена типом из целых в
целые ,может мы хотим определить эту функцию как полиморфно а ->а для
произвольных типов а.Фнукция идентификации может похоже быть огпределена
только для конкретных типов ,таких как integer:fun(x:Int)x.Мы не можем
зафиксировать тот факт ,что её форма не зависит ни от какого типа.Мы не можем
выразить идею ,что функциональная форма будет одинаковой для множества типов и
мы должны явно связвать переменные и значения определенного типа во время
,когдатакое связование может быть преждевременным.
Тот факт ,что данная функциональная форма может быть одинаковой для всх типов
может быть выражено с помощью кванторов всеобности.В частности функция
идентификации может быть определена как:
В этом определении id , а это переменная типа ,а all[a] предоставляет абстаркцию для
а ,чтобы id была функцией для всех типов.Чтобы применить функцтию
идентификации к аргументу определенного типа,мы должны сперва представить тип
как параметр и затем аргумент данного типа
(Мы использовали условие ,что параметр типа заключен в квадратные скобки ,а
аргумент – в круглые)
Мы будем определять функции такие как id ,которые требуют параметр тип прежде
чем они могут быть применены как шаблонные функции.Id это шаблонная функеция
идентификации.
Заметьте ,что all это опрератор связывания ,такой же как fun и требует сопоставления
фактического параметра при применении функции.Однако all[a] служит ,чтобы
связывать тип ,когда как fun(x:a) служит для связывания переменной данного типа.
Хотя типы применимы ,но нет предпосылок для манипулирования типами как
значениями: типы и значения все еще различны и абстракции типов и применение
типов служат для проверки типо ,без влияний на время выполнения.В
действительности мы можем опутсить информацию о типах в квадратных скобках.
Вот алгоритм проверки типов имеет задачей опознать ,что а это свободная переменная
типа и переопределить первоначальную информацию all[a] и [Int].Это часть того ,что
может делать полиморфные проверяльщик типов ,похожий на тот ,который
используется в ML. В действительности ML идет дальше и позволяет программистам
опускать даже оставшуюся информацию о типах.
ML имеет механизм выведения типов ,который позволяет системе выводить типы как
для мономорфных так и для полиморфных выражений ,поэтому определения типов
опущенные программистом может быть введены системой.Это имееет то
преимущество ,что программист может использовать сокращения нетипизированного
λ –исчисления ,тогда как система может переводить нетипизирвоанный вход в полно
типизированный вход.Хотя не существует автоматичсеких алгоритмов для выведения
типов для мощныз тпипизированных систем ,которые мы готовы рассмотреть.Чтобы
сделать понятным что происходит и не зависеть от конкретной технологии проверки
типов ,мы должны всегда записывать вс. Информацию о типах ,чтобы сделать задачу
проверки типов тривиальной.
Возвращаясь назад к явному языку ,давайте расширим нашу нотацию , чтобы можно
было явно говорить о полиморфных функциях.Мы определим тип шаблонной
функции от произвольного типа
Ниже пример функции ,которая принимает параметр произвольного типа.
Функция inst берет в качестве аргумента функцию указанную выше и возвращает два
вида её , Integer и Boolean.
В общем случае параметры функции стоящие под знаком кватнора всеобности
наиболее полезны ,когда их наддо использовать в разных типах в теле оджной
функции ,например функция вычисления длины списка передается в качестве
параметра и используется на списках разного типа.Чтобы показать некоторую свободу
,которую мы имеем при определении полиморных функций ,мы напишем две версии
twice ,которые различаются в способе передачи параметров.Первая версия twice1
имеет функциональный параметр f универсального типа. Определение
Определяет тип функционального параметра f как шаблонный тип и принимает
функции любого типа и возвращает значения этого же типа.Означенные функции f в
теле twice1 должны иметь формальный парметр типа f[t] и требуеют ,чтобы был
предоствлен фактический тип ,при означивании функции twice1.Полное определение
twice1 требует связывания параметра-типа t как универсально квантифицированного
типа и связывания x к t.
Таким образом twice1 имеет три связанные переменные ,для которых фактичсекие
параметры должны быть предоставлены во время означивания функции.
all[t] - требует фактический параметр-тип
- требует функцию типа
f(x:t) - требует аргумента подставляемого вместо типа t.
Означивание функции twice1 типа Int функцией Id и аргументом 3.
Заметьте что третий аргумент 3 имеет тип Int первого аргумента и что второй аргумент
Id универсально кватинфицированный тип. Заметьте что twice1[Int](succ) не будет
правильным так как succ не имеет тип
Функция twice2 отличается от twice1 типом аргумента f ,куоторый не универсально
квантифицирован.Сейчас нам не надо означивать f[t] в теле twice:
Сейчас возможно посчитать twice как succ.
Поэтому twice2 сперва принимает параметр –тип Int ,который служит для
конкретизировать тип функции f :Int -> Int , потом принимает в качестве параметра
функцию succ этого типа ,и потом принимает специальный элемент типа Int , которым
функция означивается дважды.Дополнительное означивание требуется для twice2 от
id, который должен быть первым конкретизирован типом Int:
Заметьте ,что как λ-абстракция так и универсальная квантификация связывают опеаторы
,котрые требуют чтобы формальные параметры были замещены действительными
параметрами.Разделение между и значениями достигается наличием различных операций
связывания для типов значений и различным скобочным синтаксисом ,когда
предоставляются фактические параметры.
Расширение λ-исчисления для поддержки разных механизмов связывания , один для
типов и один для значений и оба практически полезны при моделировании
параметрического полиморфизма и математически интересны в обощении λ-исчисления
для моделирования двух качественно отличающихя абстракций в той же математической
модели.В последующих секциях мы введем еще третий вид абстракции и
соответствующие механизмы для связывания ,но сперва нам надо ввести понятие
параметрического типа.
В Fun типв и значения строго отличаются(значения это объекты ,а типы это множества);
следовательно нам нужно 2 разных механизма связывания : fun и all.Эти две модели
связывания могут быть объединены , в некоторых моделях ,где типы это значения ,
достигая некоторой экономии конценпций , но это объединеие не подходит под нашу
базовую семантику.В таких моделях возможно объединить параметрический механизм
связывания описанный в следующей главе с fun и all.
4.2 Параметрические типы
Если у нас есть два одинаковых определения типа похожей структуры ,например:
то,мы можем вынести общую структуру в отдельное параметрическое определение и
использовать параметрический тип для поределения других типов.
Объявление типа просто вводит новое имя для выражения типа и оно эквивалентно
выражению этого типа в любом контексте.Объявление типа не вводит нового
типа.Следовательно 3,4 это IntPair ,так как оно имеет тип Int*Int – определение IntPair.
Параметрическое определение типа вводит новое определение оператора типа.Pair
сверху это оператор типа отображающий тип Т на Т*Т.Следовательно Pair[Int] это тип
Int*Int и следовательно 3,4 имеет тип Pair[Int].
Операторы типов это не типы ; они только опереируют над типами.В частности они не
должны нарушать следующие нотации:
Гдеэто оператор типа ,который когла он применяется к типу Т дает функциональный тип
T->T и В это тип функции идентификации и никогда не применяется к типам.
Операторы типов могут быть использованы в рекурсивных определениях ,как в
следующем определении шаблонного списка.Заметьте ,что мы не можем считать
List[Item] как сокращение ,которое макрорасширенным ,чтобы получить нормальное
определение.Скорее мы считать List новым оператором типа ,который определен
рекурсивно и который отображаетнекоторый тип на список этого типа.
Шаблонный пустой список может быть определен и потом конкретизирован как:
Сейчас [nil=()] имеет тип List[Item] для любого Item.Следовательно типы шаблонного nil и
его конкретных представлений
Похоже мы можем определить шаблонную функцию cons и другие операции над
списками.
Заметьте ,что cons может строить только однородные списки , так как его аргументы и
результат относятся к одному типу Item.
Мы должны упомянуть ,что существуеют проблемы в решении ,когда два определения
параметрических рекурсивныз функций представляют те же типы.[78] Описывает
проблему и и решение ,которое включает ограниченную форму определения
параметрических типов.
5. Экзистенциональная квантификация.
Опрелеление типов для переменных универсально квантифицированного типа имеет
следующую форму ,для любого типа выражения t(a):
Имеет свойство для некоторого типа а , р имеет тип t(a)
Например:
где а = Int в первом случае и а=Int*Int во втором.
Ткаким образом мы видим ,что данная константа такая как (3,4) может удовлетворять
многим экзистенциональым типам(Для дидактических целей мы присвоим здесь
экзестинционалдьный тип обычному типу напрмер (3,4)).Хотя это концептуально
правильно , в последующих секциях оно будет запрещено для для реализации проверки
типов , и мы потребуем использоования определенных конструкторов для реализации
объектов экзистенцинальных типов.Каждое значение имеет тип
,так как для каждого
значения существует тип ,такой что для этого значения есть тип.Таким образом тип
Определяет множество всех значений , которые мы должны иногда называть
Top(наибольший тип):
тип любого значения.
Множество всех упорядоченных пар может быть определено следующим
экзстенциональным типом.
тип любой пары.
Это тип любой пары p,q ,так как для некоторого типа и для некоторого типа b , p и q
имеют тип a*b.Тип любого объекта вместе с операцией возвращяющей integer , которая
может быть применена к нему может быть определен следующим экзистенциональным
типом.
Пара (3,succ) имеет этот тип , если мы возмем а=Int.Аналогично пара ([1,2,3],length) имеет
этот тип , если мы возмем а=List[Int].
Так как типы включает не толькопростые типы но еще и универсальные типы и тип Top,
ъкзистенцианально квантифицированные типы имеют некоторые свойства , котрые могут
спевар показаться не интуитивными.Тип
*а это не просто тип пар эквивадентных
типов, как некотроые могут ожидать.В действительности даже тип 3,true имеет этот
тип.Мы знаме ,что 3 и true имеют тип Top, следовательно есть тип а=Top , такой что
3,true:a*a.Поэтому тип
это тпи всех пар, также как
Также любая
ффункция имеет тип
,если мы в качестве а возмем Top.
Однако
это отношение между типом объектом и типом функией
,возвращающей integer.Например (3,length) не имеет этого типа (если посчитаем 3 типом
Top , тогда нам надо показать ,что length имеет тип Top-> Int, но мы уже знаем что , Length
имеет тип
, который отображает целочисленный список на integer , и мы не
моджем предположить ,что произкольный объект типа Top можно отобразить на Integer)
Не все экзистенуиональные типы могут быть полезными.Например если у нас есть
неизвестный объект типа
то у нас нет способов манипулирования им , так как унас
нет информации о нем . Если мы имеем неиъвестный объект типа
, то мы можем
предположить ,что это пара и пременить к ней fst и snd, но потом вызапнемся так как мы
не имеем информации о а.
Экзистенционально типипзированные объекты могут быть полезны , если они
достаточно структурированны.Например x:
предоставляет достаточную
структурированность ,чтобы над ним проводить вычисления.Мы можем выполнить:
и получить integer.
Следовательно есть полезные экзистенциональные типы , которые могут сркывать
структуру объекта ,который они предствляют , но показывают достаточную структуру
,чтобы позволять манипулировать этими объектами через оперрации ,предоставлямые
этим объектом.
Эти экзистенциональные типы могут быть использованы ,например , разнородных
списков:
Мы можем позже изъять элемент этого списка и манипулировать им , хотя мы можем не
знать ,какой конкретный элемент мы используем и какого он типа.Конечно ,мы можем
сфрмиировать разнородный спииок типа
но это достаточно бесполезно.
5.1 Экзистенциональная ква нитфикация и сокрыие информации.
Реальная польза экзистенциональных типов становится видимой , только тогда когда мв
понимаем ,что
простой пример абстракции данных с множеством своих
операций.Переменная а сама является абстрактным типом , котрый скрывает
представление.Представление было Int и List[Int] в предыдущем примере.Тогда
это множество операторов над абстрактным типом: константа типа а и
оператор типа a -> Int.Эти оперраторы не обозначены ,но мы можем ввести обозначенные
версии ,используя тип запись вместо декартова произведения:
Так как мы не знаем какого представления а ,то мы не можем ничего предположить о нем
и пользователи х не смогут использовать преимущества некоторого конкретного типа а.
Как мы объявляли ранее , мы были немного свободны в применении разных операторов
к объектам экзистеционального типа.Это будет теперь запрещено , чтоюы сделать наш
формализм более простым для проверки типов.Вместо этого мы будем иметь явные
языковые кострукции для создания и манирулироввания объектами экзистенционального
типа , так как мы имели абстракции типов all[t] и означивание типов exp[t]
Для создания и использования объектов универсальных типов.
Обычный объект (3,succ) может быть в абстрактный объект ,имеющий тип
при помощи пакетирования и поэтому некоторая структура его
скрыта.Операция pack снизу инкапсулирует объект (3,succ) , так чтобы пользователь знал
только ,что это объект типа
существует без знания фактического
объекта.Естествено подумать ,что результирующий объект имеет экзитсенциональный
тип
Пакетированный объект такой как р называется пакетом.Значение (3,succ)
Это содержание пакета.Тип
это интерфейс: он определяет структурное
пределение содержания и соответствует определению части абстракции
данных.Связывание а=Int это представление типов : оно связывает абстрактный тип
данных с конкретным представлением Int и соответствует скрытому типу данных
ассоциированный с абстракцией данных.
Общая форма операции pack
Операция pack это единственный механизм для создания объектов экзистенционального
типа.Таким образом если переменная экзистенционального типа была объявлена так :
Тогда р может брать только значения созданные операцией pack.
Пакет должен быть открыт перед тем как его использовать
Откоытие пакета вводит имя х для содержимого пакета , котрое может ьыб использовано
в области следующей после in.Когда структура х определена именованными компонетами,
к компонентам открытого пакета можно обращаться по имени.
Нам может также понадобиться ссылаться на неизвестный тип скрытый
пакетом.Например ,представьте ,что мы хотели означить второй компонент p значением
абстракного типа ,приведенного как внешний аргумент.В этом случае на неизвестный тип
b должна быть внешняя ссылка и следующая форма может быть использована:
Здесь имя типа b соответствует скрытому представлению типа в области следующей за
in.Тип выражения следующего за in не должен содержать b,чтобы b не вышло из своей
области видимости.Функция open связывает имена с представлениями типов и помогает
проверятелю типов в верификации типовых структур.Во многих ситуациях
мы захотим сократить
до р.а.Мы должны избегать таких сокращений ,чтобы
предотварщать конфликты ,но вообще они приминимы.
И pack и open не имеют влияния времени выполнения на данные.Предоставляя
достаточно умный проверяльщик типов , некоторые могут опустить эти конструкции и
вернуться к нотации ,использованной с предыдущих секциях.
5.2 Пакеты и абстрактные типы данных.
Чтобы проиллюстрировать применимость нашей нотации к реальным языкам
программирования , мы покажем как записи с функциональными компонентами могкт
быть исползованы для моделирования пакетов в Ада и как экзистенциональная
квантификация может быть использована для моделтрования абстракций данных в
Ада[83].Рассмотрим тип Point1 лдя создания геометрических точек от глобально
определенного типа Point – пары действительных чисел и для выбора х и у координат
точек.
Значения типа Point1 могут быть созданы инициализацией каждого имени функций от
типа Point1 , возвращающих функции требуемого типа.
В Ада пакет с функциями makepoint , x_coord , y_coord может быть определен так:
Определение пакета это не определение типа , но частично определение значения.Чтобы
завершить определение значений в Ада , мы должны представить тело пакета в
следующей форме:
Тело пакета предоставляет тело функций функциоальных типов в определении пакета.В
противоположность нашей нотации , которая позволяет ,чтобы разные тела функций
соответствовали разным значениям типов.
Ада не позволяте ,чтобы пакеты имели типы , и напрямую определяет тело функции для
каждого типа функции в теле пакета.
Пакеты позволяют определять группы связанных функций ,для совместного
использования локальных скрытых струтур данных.Например пакет localpoint , чья
локальная структрура данных point имеет следующую форму:
Package body localpoint is
Point :Point ; -- разделяемая глобальная переменная функций marketpoint , x_coord
,y_coord
Скрытые локальные переменные могут быть реализованы в нашей нотации при помощи
конструкции let.
Хотя Ада не имеет концепции типа пакета ,она имеет определение понятия шаблона
пакета , который имеет некоторые ,но не все свойства типа.Шаблоны пакетов вводятся
ключевым словом generic
Значения point1 и point2 шаблонного пакета Point1 могут быть введены как:
Все значения пакетов ,соответствующих данному шаблонному пакету имеют одно и тоже
тело пакета.Определение пакета в Ада статически соостветствует его телу до исполнения,
тогда как типизированные значения записей динамически соответствует телам функций
,когда выполняется комманда создания значения.
Компонеты значений пакетов созданных из шаблонного пакета может быть достигнута
используя нотацию записей.
Таким образом пакет похож на значения записи в том ,что разрешает доступ к своим
компонентам однинаковых нотаций , как это делается при выборе компонентов записи.Но
пакеты это не первостепенные объекты в Ада.Они не могут передаваться как параметры в
процедуры , не могут быть компонентами массива или записей и не им могут быть
присвоены значения переменных-пакетов.Более того шаблонные пакеты это не типы ,
хотя похожи на них , в том что позволяют создавать экземпляры.На самом деле в Ада есть
2 похожих ,но неколько различающихся механизма для управления структурами
похожими на запись , один для управления данными записи с соответствующими типами
записи ,а другой для управления пакетами с соответствующими шаблонами.Сопоставляя
эти два механизма в Ада для типов записи и шаблонных пакетов с единым механизмом в
нашей нотации мы добиваемся понимания преимуществ единог расширения типов до
записей с компонетами-функциями.
Пакеты в Ада , которые просто инкапсулируют множество операций на публично
определенном типе данных , не требует придумывать операторы типа.Они могут быть
смоделированы в нашей нотации простым типизированным λ-исчислением без кванторов
существования.Только когда нам может понадобиться спрятать представление типа
используя private типы , тогда нужны кванторы сущсествования.
Конструкция let в предыдущем примере использовалась для реализации сокрытия
информации.Мы называем это сокрытие информации первого порядка ,так как это
достигается ограничением области действий уровня значений.Это можно
противопоставить с сокрытием инфоормации второго порядка ,которое реализуется с
помощью кваторов существования , которые ограничивают область действия на уровне
типов.
В Ада пакет point2 с приватным типом Point может быть определен как :
Приватный тип Point может быть смоделирован при помощи кваторов существования.
Иногда удобно представлять определение типа экзистенционально квантифицированного
типа как параметрическую функцию от скрытого параметра-типа.В примере мы можем
определить Point2WRT[Point] так:
Нотация WRT в Point2WRT должна читаться как , подчеркивает тот факт ,что это
определение типов соотносится с парамертром-типом. Значие point2 экзистенционального
типа Point2 может быть создан операцией pack.
Оперция pack скрывает представление Real*Real типа Point и имеет экзистенционально
параметризируемый тип Point2WRT[Point] как часть своего определения и предоставляет
как свой скрытые тело предыдуще определеного типа point1 , который реализукет
операции для данного представления данных.
Заметьте , что Point2WRT[Point] представляет параметризированный типовое выражение
,которое предоставляется с фактическим параметром таким как Real , устанавливает тип (
в случае типа записи с тремя компонентами).Отношение между ътим типом
параметризации и дургим видом параметризации представленным только недавно
проиллюстрированы ниже:
1.Функциональная абстракция : fun(x:type) value-expr(x).Параметр х это значение и
результат подстановки фактического параметра вместо формального определяет значение.
2.Квантификация :all(a) value-expr(a).Парамеир а это тип и результат подстановки
фактического параметра вместо формального определяет значение.
3.Абстракция типов.TypeWRT[T] = type –expr(T).Параметр Т это тип ,результат
подстановки формального параметрав вместо фактического тоже тип.
Формальные параметры должны быть типами ,тогда как фактические могут быть
любыми значениями.Одонако ,когда класс именованных типов расширен ,чтожы
включать и экзистенционально квантифицируемые типы , также это дает то,что
аргументы могут подставлятся вместо формальных параметров.
Экзистенцональная квантификация может быть использована для моделирвания
приватных типов в Ада.Однако , они более общие нежели чем средства абстракции
данных в Ада , как покахано в следующем примерах.
5.3 Комбинирования универсальной и экзистенциональной квантификации.
В этой секции мы дадим пример ,который демонсрирует взаимодействие между
экхистенциоальной и универсвальной квантификацией.Универсальная квнтификация
возвращает шаблонный тип , тогда как экзистенциональная – абстрактный тип.Когда жти
нотации скомбинированы мы получаем парарметрическую абстракцию данных.
Стеки это идеальный пример ,иллюстрирующий взаимождействия между шаблонными
типами и абстракциями данных.Простейшая форма стека имеет оба специфических типа
элемента таких как integer , и специфичную реализацию структуры данных ,такую как
список илил массив.Шаблонные стеки праметризируют тип элемента ,а скрытые
структуры реализованы комбинацией унпиверсальной квантификации для реаклизации
параметризации с экзистенциональной квантификацией для реализации абстроакций
данных.Следующие операции надю списками и массивами будут использованы.
Мы начинаем с конкретного типа IntListStack c целочисленными элементами и списковым
представлением данных.Этот конкретный тип может быть реализован как кортеж
операций без всякой квантификации.
Пример стека с компонетами инициализированными как специфические функциольные
занчения может быть определен как:
Мы также можем иметь стек целых чисел в виде пар состоящих из массива и верхнего
индепкса стека ,это конкретный тип может быть опять реализован как кортеж без какойлибо кевантификации.
Пример IntArraySTack это пример типа кортежа с полдями операции
инициализиваронными операциями представления стека в виде массива.
Конкретный стек выше может быть обощен элементы шаблонеными типами и скрывая
реализацию стека.Следцющий пример илюстрирует как шаблонный тип моэет быть
реализован универсальной квантификацией.Сперва мы определим тип GenericListSTack
Как универсально квантифицируемый тип.
Экземпляр этого универсального типа может быть создан при помощи универсальной
квантификации экземипляра записи , чьи поля ининциализированы операциями
параметризированными шаблонным универсально квантифицированныфм параметром.
genericListStack имеет ,как и предпологает его имя , конкретныю списковую реализацию
структуры данных стека.Альтернативный тип GenericArrayStack с конкретной
реаклизацией в виде массива может быть определен аналогично.
Так как предстваление данных стека не важно для пользователя , то мы спрячем его ,так
чтобы интерфейс стека это независимое скрытое представление данных.Мы будем
использовать единственный тип GenerucStack , который реализован как шаблонный
списковый стек.Пользователи GenericStack не должны знать какую реализацию
GenericStack они используют.
Вот здест нам нужны экзистенциональный типы.Для любого элемемнта типа должна
существовать реализация стека ,которая предствляет все операции над стеком.Ээто
реализуется в типе GenericStack определенном в терминах универсапльно
квантифицированного параметра Item и экхистенционально квантифицированного
параметра Stack :
Тип с двумя параметрами GenericStackWRT[Item][Stack] может быть в свою очередь
определен как кортеж двойных параметризированный операций.
Заметьте ,что нет ничего в жтом определении ,чтобы различать роли этих двух параметров
Item и Stack.Однако в определении GenericStack параметр Item универсально
квантифицирован , показывая ,что он представляет скрытый абстрактный тип данных.
Мы можем сейчас абстрагировать наши genericListStack и genericArrayStack пакеты в
пакет типа GenericStack:
Оба listStackPackage и arrayListPackage имеют один и тот же тип и мало отличаются
формой представления скрытых данных.
Более того функции такие как useStack могут работать без знания о реализации:
И можно дать любую реализацию GenericStack как параметра:
В определении GenericStack тип Stack в большинстве не относится к Item , но наша цель в
том ,что какая бы не была реализация Stack ,стеки должны быть коллекциями
элементов.Из-за этого возможно построение объектов типа GenericStack ,где стеки не
могут ничего джелать с эелементами , и не подчиняются свойствам таким как :
Это ограничение является правильным в более мощных системах таких
как {86] и [84] ,где можно деалть абстрактными операторы типов вместо самих типов , и
некоторые могут напрямую выражать ,что предствление Stack должно быть онсовано на
Item (но даже в таких более выразительных системах типов возможно возможно так
подделать пакеты стека ,что он не будет подчинятся свойствам стека)
5.4 Квантификация и модули
Сейчас мы уже готовы для большого примера: геометрические точки.Мы введим
абстрактный тип с поерациями mkpoint(создает новую точку от двух действительных
чисел), x-coord и y-coord (возвращает x-ую и у-ую координаты):
Наша цель определить значения этого типа ,которые скрывают и представление PointRep
и реализацию поераций mkpoint , x-coord и y-coord по отношению к этому преставлению.
Чтобы завершить это мы определим тип этих операций как параметрические типы с
PointRep в качесвте параметра.Имя типа PointWRT подчеркивает ,что операции
определены по отношению к определенномупредставлению и наоборот асьтрактный тип
Point независит от представления.
Экзистенциональный тип Point может быть определен в терминах PointWRT при помощи
экзистенциональной абстракции по отношению к PointRep.
Отношение между представления-зависимыми операциями и соответствующими
асбстрактными типами данных становится более понятным ,когда мы
проиллюстрируемпроцесс абстракции для некоторых специфичных представлений точек.
Давай те определим пакет декартовой точки , в котором представление точки это пара
децствительных чисел ,операции над которыми mkpoint , x-coord , y-coord выглядят так:
Пакет с представлением точки Real*Real и с реализаций операций над точками может
быть опеределен следующим образом:
Также мы можем определить пакет полярных точек , чье представление такое же как и
декартовой точки , но другое представление операций над точками:
Этипримеры иллюстрируют как пакеты реализуцбт абстракции данных путем сокрытия и
представления данных и реаллизации операторов.Пакеты декартовых и полярных точек
имеют один и тот же экзистенциональный тип Point , сипользуют один и тот же
параметрический тип PointWRT[PointRep] для определения структуры операций над
точками и имеют один и тот же тип для представления данных.Они только отдичаются
содержпнием пакета ,который определяет реализацию функции.В общем случае
экзистенциональные типы влияют на то ,чтоюы пакеты таких типов имели одинаковую
структуру для операций.Но и типы для представления внутренних данных и реализация
операций может отличаться для разных реализаций абстракнтных типов данных.
Абстрактный тип данных спакетированный со своими операциями , так же как Point
,это также простой пример модуля.В общем случае модули могут импортировать другие
модули , или могут быть параметрихированными по отнозения к други модулям.
Парметрические модули могут быть представлены как функции над
экзистенциональнымии типами.Здесь приведен пример того как Point пакет может быть
расширен еще одной операцией add.Вместо того чтоюбы делать это расширение для
конкретного пакета Point , мы напишем процедуру для расщирения любого паекта Point
над неизвестным пресдтавлением Point.Вспомниет ,что & это оператор конкантенации
типа запись:
Сейчас мы вернемся к модулю Point и покажем как другие можули могут быть построены
на его верхушкею.В частности мы посроем модули Circle и Rectangle на верхушке модуля
Point и потом определим модуль Picture ,котрроый использует и Circle и Rectagle.Люббые
экземпляры Point могут быть основаны на произвольных представлениях данных , мы
должны удостоверится ,что круги и прямоугольники основаны на одинаковых
представалениях Point , если мы хотим ,чтобы они взаимодействовали.
Пакет круг предоставляет опрерации для создания круга из точки (центр) и
действительного числа(радиус) и операции которые возвращают центр и радиус круга.
Операция diff(расстояние между центрами двух кругов ) также определена.Два параметра
diff это круги основанные на одинаковых представлениях Point.Пакет круг также
предоставляет пакет точка , чтобы позволять иметь доступ к операциям надд точками.
Мы теперь можем постоить некотрорый пакет круг , применив circleModule к различным
пакетам точкам.Мы можем также определить различные версии circleModule основанные
на различных представлениях круга и все это можно применить к различным пакетам
точкам ,чтобы получить пакеты круги.Здесь мы применяем circleModule к
cartesianPointPackage и к polarPointPackage для получения пакетов декартовых и полярных
точек.
Чтобы испльзовать пакеты точек мы долоны спвар их открыть.Мы должны открыть их два
раза ,чтобы(заметьте что тип Circle имеет двойную экзистенциональную квантификацию)
для связывания PointRep и CircleRep с представлениями точки и круга используемых в
пакете.Здесь мы используем сокрашщенную форму open ,которая эквивалентна
последовательным открытиям.
Прямоугольник опредделяется двумя точками , верхняя левая и правая
нижняя.Определение можулля прямоугольника очень похоже на определение модуо\ля
круга.В добавление к жтотму мы должны удостоверится ,что две точки определяющие
прямоугольник основаны на одинаковом представлении Point.
Сейчас мы соединим все это вместе в можуль фигур , котрый испольлзует круги и
прямоугольники (основанных на едином представлении точек) и поределеяет операцию
boundingRect ,возвращающую наименьший прямоугольник содержащийся в данном круге.
5.5 Модули это зачения первого класса.
В предыдущих секциях мы показали ,что пакеты и модули это объекты первого класса:
Это правильные занчения которые могут быть переданы т возвращены функциями и
хранимы в объектах.Например , возможно писать программы , зависящие от улсовий ,
производящих один или другой пакет от одного и того же экзистнционаьного типа
реализующего интерфейс и возвращать его для использования в конструкции бюолее
больших пакетов.
Процесс линкования модулей может быть тоже выражен : мы делали это в
предудущем примере , например мы произвели cartesianCirclePackage линкуя
cartesianPointPackage и circleModule.Следовательно процесс построения систем из
модулей можкт быть выражен на том же языке на котром и программирруются модули и
вся мощь языка может быть применима вы процессе линковки.
Хотя мы показали ,что мы можем выразить параметрические модули и механизм
линковки ,мы не говорим ,что это самая удобная нотация.Наша цель показать ,что все эти
концепции могут быть выражены в простой среде.Наиболшая проблема здесь это то ,что
неокоторые должны опасаться зависимости модулей при создании нового экземпляра
модуля , и линкование должно происходить вручную для каждого нового экземпляра.Эти
проблемы уже поставлены в механизме модулей в ML.
6.Ограниченная квантификация.
6.1 Включение типов ,поддиапозоны и наследование.
Мы говорим ,что тип А включен или подтип другого типа B ,когда все значения А также
являются значеиями типа В , то есть А как множество значений жто подмножество В.Это
общее определение включения различает азличные правила включения для разных типов
конструкторов.В этой секции мы обсудим включение поддиапозонов ,записей , вариантов
и функциональных типов.Включения универсально и экзистнционально
квантифицированных типов обсуждается в последующих главах.
Как вступление к включению на типах записей , мы сперва представим постейшую
теорию включений на целых поддипозрнах типов.Пусть n..m определяют подтип типа Int
Соответствующий поддиапозонам от n до m включая конечные точки ,где m и n это
произвольные целыеСледующие отношения включения справедливы для целых
поддиапозонов типов:
Поддиапозоны типов могут появится как орпеделение типов в λ-выражениях.
Константа 3 имеет тип 3..3 и также имеет тип любого супертипа включая тип x:2..5 выше.
Поэтому это правильный аргумент в f.Также следующее тоже должно быть правиьлно:
так как тип у это подтип области определения f.Фактический параметр функции может
иметь любой подтип соответствующего формального параметра.
Рассмотрим функциональный тип 3..7 -> 7..9 .Также это модно посчитать функцией
типа 4..6 –>6.10, так как она отображает целые между 3 и 7 (и следовательно между 4 и 6)
На целые между 7 и 9 (и следовыательно между 6 и 10).Заметьет ,что домен сужается
,когда как подомен расширяется.В общем случае мы можем сформулировать правила
включения следующим образом:
Заметьетпохожесть этого правила и правила для поддиапозона , и как включение меняется
на домене.Интересная вещь на счет этих правил включения это то,что они постоянно
работают для более высоких функционпльных типов.Например
Может быть означено f: h(f)
Из-за этих правил включения для поддиапозонов , стрелок и приложений.
То же рссуждение подходит и для типа запись.Предположим у нас есьт типы:
Мы хотим показать ,что все мащины это транспорт например Car это подтип Vehicle.Для
достижения этого нам потребуется следующее правило включения для типа запись.
Например тип-запись А это подтип другой записи В ,если А имеет все поля ,что имеет В ,
и возможно больше ,и типы общих полей связаны отношением подтипов.Значение типа
Vehicle это множество всех записей ,которые имеют по крайней мере целочисленное поле
Age и одно целочисленное поле speed и возможно более.Следовательно любая машина в
этом множестве , и множество всех машин это подмножество множества транспорт.Опчть
подтипы это подмножества.Выделение подтипов в записях соответствует концепции
наследования в языках ,особенно если записи имеют функциональные
компоненты.Пример класса это запись с функциями и локальными переменными , а
пример собкласса это запись по крайней мере с этими же функциями или переменными и
даже больше.
Мы можем даже выразить множественное наследование.Если мы добавим определения
типов:
тогда car это подтип типов vehicle и machine ,а эти в свою очередь подтипы типа
object.Наследование на записях также расширяется для более высоких функциональных
типов , в случае поддиапозонов , правило включения для постранства функции тоже
поддерживается.
В случае вариантных типов мы имеем следующее правило включения.
Например ,любой яркий цвет это цвет :
и любая функцие работающей с цветами будет доступны и яркие цвета.
Более детальные детали этих типов наследования могут быть найдены в [84b].
6.2 Ограниченная универсальная квантификация и выделение подтипов.
Сейчас мы подошли к проблеме ,как смешать выделение подтипов и параметрический
полиморфизм.Мы видели уже полезность этих двух концепций в различных
приложениях;и сейчас мы покажем как это полезно и иногда необходимо соединять их.
Возмем простейшую функцию от записи с одним компонентом:
Который можетможет быть применен к записям {one=3,two=true}.Это можно сделать
полиморфным:
Мы можемиспользовать f[t] на записях формы {one=y} для любого типа t , и на записях
типа {one=y , two=true}.
Нотация all[a]e позволяет нам выражать правило ,что переменная типа может принимаь
любое значение типа,но не позволяет нам распознавать переменные типов , которые
принадлжат подмножеству множества типов.Общее средство для определения
переменных ,которые определены на произвольных подмножествах типов может быть
реализовано как квантификация над множествами типов определенных специальными
предикатами.Но нам не нужна такая общность и мы можем удовлетворится определив
конкретный класс множеств – множество всех подтипов данного вида.Это может быть
сделано ограниченной квантификацией.
Переменная типа определенная на множестве всех подтипов типа Т может быть
определена путем ограничения квантора квантора :
а определено на всех подтипах Т в области е.
А вот здесь функция ,которая принимает любую запись имеющую целочисленный
компонент one и возвращает её содержимое:
Заметьте ,что немного отличий между go и fo , все что мы сделали это наложили
ограничения ,что аргумент должен быть подтипом {one:Int} от параметра fun к параметру
all.Сейчас мы миеем два способа выражения огрничений включения : неявно путем
параметров функции и явно путем ограниченных кванторов.Теперь ,так как у нас есть
ограниченные кванторы ,мы можем забыть другие механизмы , требующих строгого
сопоставления типов при передачи параметра , но мы оставим его для удобства.
Чтобы ввыразить тип нам нужно ввести оганиченные кванторы в выражение типа:
Теперь у нас есть способ как для выражения наследования так и для выражения
параметрического полиморфизма.Вот новая версия , в которой мы абстрагируем Int в
любой тип:
где all[b]e теперь сокращение для
Новая функция g не может быть
выражена при помщи параметрического полиморфизма или отдельно
наследованием.Только их комбинация , достигаемая при помощи огрниченных кванторов
, позоляет нам это записать.
Однако ,ограниченные кванторы не показали какой-то дополнительной силы ,так как мы
можем перефразировать как и g как f , дав нам включение типов при передаче
параметров.Но ограниченные кванторы более выразительны ,как показывают следующие
примеры.
Необходимость в ограниченных кванторах появляется очень часто в объектно –
ориентированном программровании.Предположим ,что мы имеем следующие функции и
типы:
Это типично для объектно-ориентированных языков повторно использовать
функции такие как moveX на объектах ,чей тип был неизвестен ,когда определялась
функция moveX.Если мы сейчас определим :
мы можем захотеть использовать функцию moveX плитки ,а не только точки.Однако если
мы будем использовать более простую функцию moveXo , это только звучит чтобы
предположить ,что результат будет точкой ,даже если параметр был плиткой и мы
разрешаем включение для функциональных переменных.Следовательно , мы теряем
информацию о типе ,если передаем плитку через функцию moveXo и например мы не
можем в дальнейшем возвращать компонент hor из результата.
Ограниченная квантификация позвроляет нам лучше выражать зависимости
входа/выхода: тип функции moveX будет такой же как и у его аргумента , каким бы не
был тип Point.Следовательно мы можем применить moveX к плитке и возвратить плитку
без потери информации о типе.
Это показывает ,что ограниченная кватификация полезна даже в отсутствии подходящего
параметрического полиморфизма для выражения подтиповых отношений.
Ранее мы видели ,что параметрический полиморфизм может быть тоже явным
(используя кванторы всеобщности) или неявным(имея любые переменные типов).Здесь
мы имеем похожую ситуацию , где наследование может также быть явным , используя
ограниченную квантификацию , или левым неявным в правилах включения для передачи
параметров.В объектно-ориентированных языках ,параметры подтипов в общем
неявные.Мы можем рассмотреть такие языки как сокрщенные версии
языковиспользующих ограниченную квантификацию.Таким образом ограниченная
квантификация полезна не только для увеличения выразительнойй силы ,но также ,чтобы
сделать явным мехнанизм параметров ,через которую достигается наследование.
Download