Илья Ермаков писал(а):
Так вот, если брать компилируемые реализации ФЯ, то автовывод типов - это всё равно, что превратить каждую обычную функцию в template. Т.к. потенциально множество типов, с которыми функция может работать, бесконечно и не зафиксировано жёстко в определении самой функции.
Совершенно неверно!
В таких языках, как тот же Хаскелл (у которого самая правильная, на мой взгляд, организация системы типов), тело функции явно и очень строго ограничивает типы аргументов и результата.
Конечно, есть такие функции, как map или filter, у которых ограничения очень широкие, но таких функций не так уж и много, да и то это возможно лишь из-за простоты самих этих функций.
Возьмём map:
Код:
map f [] = []
map f (x:xs) = (f x) : (map f xs)
Что мы видим в реализации этой функции? А то, что первый аргумент -- некая функция, тип которой не указан, а значит может быть в самом общем случае таким:
Код:
f :: a -> b
т.е. f принимает значение любого типа (обозначенного как a) и возвращает значение любого типа (обозначенного как b), причём эти два типа могут и совпадать.
Второй аргумент -- список значений неуказанного типа, обозначаемого, ну например, как a. Функция map применяет первый аргумент к элементам второго аргумента, и результаты склеивает в список-результат.
Отсюда делается вполне логичный автовывод, что тип функции map таков:
Код:
map :: (a -> b) -> [a] -> [b]
т.е. первый аргумент -- функция с типом (a -> b), второй аргумент -- список значений типа a, результат -- список значений типа b...
Да, типы тут могут быть самыми разными, но строго определено, что первый аргумент -- функция от одного параметра, и ничто иное, а второй аргумент -- список значений, которые можно подать на вход первому аргументу. Результат же функции map -- список значений того типа, который возвращается первым аргументом.
Где тут могут быть проблемы? Эта функция проста и сверхнадёжна. И даже у такой простой функции типы аргументов и результатов строго ограничены, хотя при этом она полиморфна...
Далее, не забывайте, что типы в Хаскелле принято объединять в группы, называемые классами типов. В более сложных примерах обычно используются методы некоторых классов, которые тут же накладывают ограничения на аргументы и результаты определяемых функций.
Возьмём для простоты такой пример:
Код:
inc x = x + 1
Как Вы уже догадались, функция inc просто возвращает число, которое на единицу больше аргумента. Здесь используется операция (+) -- метод класса типов Num. Число 1 не содержит дробной части, поэтому может быть как целым, так и вещественным, и никак не ограничивает множество типов значений, которые могут обрабатываться методом (+). Поэтому аргумент функции inc должен иметь тип, являющийся представителем класса чисел Num, и тип функции inc таков:
Код:
inc :: (Num a) => a -> a
Что означает, что и параметр функции inc, и её результат имеют один и тот же тип, являющийся экземпляром класса Num.
Более сложный пример:
Код:
square r = pi * r * r
Да-да, эта функция просто вычисляет площадь круга, задаваемого радиусом r. Я намеренно не использовал операцию возведения в степень, что бы упростить пример. Так вот, тут уже используется константа pi, тип которой pi :: (Floating a) => a. Эта константа на самом деле полиморфная функция (а точнее -- метод класса Floating), у которой нет параметров, а результат может иметь значение любого типа, для которого определёна реализация интерфейса класса типов Floating.
Операция (*) является методом класса Num; в определении класса Floating указана его зависимость от класса Fractional, который в свою очередь зависит от класса Num. Эти зависимости означают, что на типы аргументов метода (*) в данном случае накладываются ограничения -- они должны быть экземплярами не только класса Num, но так же и классов Floating и Fractional. Так как ограничение на класс Floating автоматически ведёт к ограничениям на классы Num и Fractional, то они опускаются из описания выведенного транслятором типа функции square:
Код:
square :: (Floating a) => a -> a
Это не является угрозой для надёжности программы, так как Вы не сможете указать, что какой-то тип принадлежит к классу Floating, не указав при этом его принадлежность к классам Fractional и Num...
В результате мы имеем полиморфную функцию square, тип аргумента и результата которой один и тот же и при этом -- некое вещественное число с представлением в виде с плавающей запятой.
Собственно, я хочу сказать, что современные функциональные языки с автовыводом типов имеют строгую статическую типизацию, даже более строгую, чем у того же Оберона, но при этом более гибкую. Строгость и гибкость могут сочетаться! :о)
Илья Ермаков писал(а):
Таким образом, ставьте сразу крест не только на динамическом расширении системы, но и даже на раздельной компиляции.
А вот этот момент я не понял. В Хаскелле вполне нормальная система раздельной компиляции. Не хуже, чем в Оберонах...
Способ создания расширяемых программ на Хаскелле я ещё не изучал, есть какая-то библиотека hsplugin, всё не дойдёт у меня дело до неё добраться…