Standart ML. Урок 11. Полиморфизм и перегрузки

Standart ML   28 октября 2013  Автор статьи:  
Полиморфный тип
  • В 6 уроке, когда мы разбирали частные случаи списков, выяснили, что есть полиморфный и тип, иначе политип, содержащий переменные типа и получающийся путем замены всех вхождений данной переменной типа на какой-либо (один и тот же) тип.
  • Так же мы выяснили, что тип, подставляемый вместо переменной может быть полиморфным.
  • Приводили пример политипа. Напомню, типы real list и (real * ’b) list являются частными случаями политипа ’a list.
Полиморфная функция

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

Функция join получает в качестве аргумента два списка и в результате возвращает один список:

- fun join (nil, sp) = sp
| join (head :: tail, sp) =
head :: join (tail, sp);
> val 'a join = fn : 'a list * 'a list -> 'a list

Функция join соединят два списка, прикрепляя второй список в конец первого:

- join ([21, 31, 51], [2,4,6]);
> val it = [21, 31, 51, 2, 4, 6] : int list

Функция join может быть применена к элементам любого типа (1. функция полиморфного типа, отсюда определение типа включает переменную ‘a):

- join (["функция", ","], ["я", "использую", "тебя"]);
> val it = ["функция", ",", "я", "использую", "тебя"] : string list

Помним, что типы элементов списков-аргументов должны совпадать!

[important]Тип полиморфной функции — всегда политип![/important]

Перегрузки

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

Пример перегрузки — функция умножения (*). Например, произведение целых чисел (4 и 7) записывается как 4 * 7, действительных (4.0 и 7.0) — как 4.0 * 7.0.

В чем парадокс? — Алгоритм перемножения целых чисел отличается от алгоритма перемножения действительных чисел. Таким образом, один и тот же символ * используется для обозначения двух различных функций (а не одной полиморфной). В каждом конкретном случае выбор функции для использования зависит от типа аргументов.

Отсюда вытекает, что писать

- fun mul (x, y) = x * y;
> val mul = fn : int * int -> int

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

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

Для функции mul:


- fun mul (x : real, y : real) = x * y;
> val mul = fn : real * real -> real

или


- fun mul (x, y) : real = x * y;
> val mul = fn : real * real -> real

И в первом и во втором случае результат естественно будет одним и тем же:


- mul (4.0, 7.0);
> val it = 28.0 : real

Заметим, что в функции переменные типа записываются так же, как их выводит компилятор SML.

[important]Что интересно![/important]

Аргументы функции могут быть записаны без скобок (и запятой между аргументами)! Тогда при вызове функции скобки так же не нужно будет ставить, что упрощает ввод:


- fun mul x y : real = x * y;
> val mul = fn : real -> real -> real
- mul 4.0 7.0;
> val it = 28.0 : real

[note]Не было бы перегруженных функций, не было бы необходимости явно указывать типы.[/note]

Равенство и перегрузки. Квазиполиморфный путь

Равенство не является полиморфной функцией, но лишь только в том смысле, в каком функция join является полиморфной.

Равенство определено почти для всех типов.

[important]Тип допускает проверку на равенство тогда и только тогда, когда для любых двух значений этого типа можно проверить равны они или нет.[/important]

Да, не все типы допускают проверку на равенство (функциональный тип), однако для всех типов, допускающих проверку на равенство (атомарный тип), существует функция =, которая возвращает true или false в зависимости от того, равны или нет сравниваемые значения.

Компилятор Standart ML позволяет использовать равенство «квазиполиморфным» путем, т.к. сам может определить, допускает тип проверку на равенство или нет. Для этого вводятся переменные типа, записываемые как ”а, которые пробегают множество всех (допускающих проверку на равенство) типов. Далее компилятор отслеживает, нужно ли чтобы какой-либо тип допускал проверку на равенство, отражая данный факт в выведенных типах.

Функция foundX — функция поиска элемента в списке:

- fun foundX (x, nil) = false
| foundX (x, head :: tail) =
if x = head then true
else foundX (x, tail);
> val ''a foundX = fn : ''a * ''a list -> bool
- m (1, [2, 42, 14, 1, 18]);
> val it = true : bool
- m ("yes", ["no", "ye", "s", "yes", "noOrYes"]);
> val it = true : bool

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

В следующем уроке мы научимся определять новые типы в Standard ML!

Научиться программировать

  • на Delphi

  • на Java

  • на C++