Объяснить вывод типа ML программисту на C++
Как ML выполняет вывод типа в следующем определении функции:
let add a b = a + b
Это как шаблоны C++, где проверка типов не выполняется до момента создания экземпляра шаблона, после которого, если тип поддерживает необходимые операции, функция работает или же выдается ошибка компиляции?
например, следующий шаблон функции
template <typename NumType>
NumType add(NumType a, NumType b) {
return a + b;
}
будет работать на
add<int>(23, 11);
но не будет работать на
add<ostream>(cout, fout);
Это то, что я думаю, правильно или вывод типа ML работает по-другому?
PS: извините за мой плохой английский; это не мой родной язык.
4 ответа
Я предлагаю вам взглянуть на эту статью: что такое Хиндли-Милнер? (и почему это круто)
Вот простейший пример, который они используют для объяснения вывода типа (это не ML, но идея та же):
def foo(s: String) = s.length
// note: no explicit types
def bar(x, y) = foo(x) + y
Просто взглянув на определение bar, мы легко увидим, что его тип должен быть (String, Int)=>Int. Это вывод типа в двух словах. Прочитайте всю статью для получения дополнительной информации и примеров.
Я не эксперт по C++, но я думаю, что шаблоны - это что-то еще, что ближе к универсальности / параметричности, а это нечто иное.
Я думаю, что попытка связать вывод типа ML с чем-нибудь в C++ с большей вероятностью приведет к путанице, чем к пониманию. В C++ просто нет ничего похожего на вывод типов.
Единственная часть C++, которая не делает типизацию явной, - это шаблоны, но (по большей части) они поддерживают общее программирование. Шаблон функции C++, который вы дали, может в равной степени применяться к неограниченному набору типов - например, к коду, который вы используете. NumType
в качестве параметра шаблона, но будет работать со строками. Одна программа может создать экземпляр add
добавить две строки в одном месте и два числа в другом месте.
Вывод типа ML не предназначен для общего программирования. В C или C++ вы явно определяете тип параметра, а затем компилятор проверяет, что все, что вы пытаетесь сделать с этим параметром, разрешено этим типом. ML переворачивает это: он смотрит на то, что вы делаете с параметром, и выясняет, каким должен быть тип, чтобы вы могли делать эти вещи. Если вы пытались делать вещи, которые противоречат друг другу, он скажет вам, что нет типа, который мог бы удовлетворить ограничения.
Это было бы почти невозможно в C или C++, в основном из-за всех неявных преобразований типов, которые разрешены. Просто например, если у меня есть что-то вроде a + b
в ОД можно сразу заключить, что a
а такжеb
должны быть целыми числами - но в C или C++ они могут быть практически любой комбинацией целочисленных или указательных или плавающих типов (с ограничением, что они не могут быть указателями) или использовать определенные типы, которые перегружают operator+
(например, std::string
). В ML поиск типов может быть экспоненциальным в худшем случае, но почти всегда довольно быстрым. В C++ я бы оценил его как экспоненциальный гораздо чаще, и во многих случаях он, вероятно, будет недостаточно ограничен, поэтому данная функция может иметь любую из множества разных сигнатур.
ML использует типовой вывод Хиндли-Милнера. В этом простом случае все, что нужно сделать, это посмотреть на тело функции и увидеть, что она использует +
с аргументами и возвращает это. Таким образом, можно сделать вывод, что аргументы должны быть типом аргументов, которые +
принимает (т.е. целые числа), и функция возвращает тип, который +
возвращает (также int). Таким образом, предполагаемый тип add
является int -> int -> int
,
Обратите внимание, что в SML (но не в CAML) +
также определен для других типов, кроме int, но он все равно будет выводить int, когда существует несколько возможностей (т.е. add
определенную вами функцию нельзя использовать для добавления двух чисел с плавающей запятой).
F# и ML несколько похожи в отношении вывода типов, так что вы можете найти
полезно.