Это довольно глубокий вопрос с множеством разветвлений, и я не хочу писать научную статью здесь. Я просто поцарапаю поверхность и укажу вам больше информации в другом месте. Я основываю свой ответ на личном опыте с Glasious Glasgow Haskell и Standard ML из Нью-Джерси , а также на научных работах, написанных об этих системах.
Ключевым отличием амбициозного компилятора является различие между известными вызовами и неизвестными вызовами. Для языков с функциями более высокого порядка второстепенное, но все же важное различие заключается в том, является ли вызов полностью насыщенным (что мы можем решить только на известном сайте вызова).
A известный вызов означает сайт вызова, где компилятор точно знает, какая функция вызывается и сколько параметров он ожидает.
неизвестный вызов означает, что компилятор не может выяснить, какую функцию можно вызвать.
Известный вызов полностью насыщен , если вызываемая функция получает все ожидаемые параметры и переходит прямо к коду. Если функция получает меньше аргументов, чем ожидает, функция применяется частично , и вызов приводит только к выделению замыкания
Например, если я напишу функции Haskell
mapints :: (Integer -> a) -> [a]
mapints f = map f [1..]
тогда вызов map
является известным и полностью насыщенным .
Если я напишу
inclist :: [Integer] -> [Integer]
inclist = map (1+)
, тогда вызов map
известен и частично применен .
Наконец, если я напишу
compose :: (b -> c) -> (a -> c) -> (a -> c)
compose f g x = f (g x)
тогда оба вызова f
и g
оба неизвестны .
Главное, что делают зрелые компиляторы - это оптимизация известных вызовов . В вашей классификации выше эта стратегия в основном подпадает под # 2.
Если все сайты вызова функции известны, хороший компилятор создаст специальное соглашение о вызовах только для этой функции, например, передав аргументы только в правильные регистры, чтобы сделать вещи хорошо потренируйся.
Если известны некоторые, но не все сайты вызовов функции, компилятор может решить, что стоит создать специальное соглашение о вызовах для известных вызовов, которое будет либо встроенным, либо будет использовать специальное имя, известное только компилятору. Функция, экспортируемая под именем в исходном коде, будет использовать стандартное соглашение о вызовах, и ее реализация обычно представляет собой тонкий слой, который выполняет оптимизированный хвостовой вызов специализированной версии.
Если известный вызов не полностью насыщен, компилятор просто генерирует код для выделения замыкания прямо в вызывающей стороне.
Представление замыканий (или то, обрабатываются ли функции первого класса с помощью какой-либо другой техники, такой как лямбда-лифтинг или нефункционализация) в значительной степени ортогонально обработке известных вызовов против неизвестных.
(Возможно, стоит упомянуть альтернативный подход, используемый MLton : это компилятор всей программы; он видит весь исходный код; он сводит все функции к первому порядку, используя технику Я забыл. Есть до сих пор неизвестных вызовов, потому что общий анализ потока управления на языках более высокого порядка неразрешим.)
По поводу ваших последних вопросов:
Я думаю, что эта проблема - только один аспект грязной проблемы, называемой «как компилировать первоклассные функции». Я никогда не слышал специального названия только для этой проблемы.
Да, есть и другие подходы. Я набросал одну и упомянул другую.
Я не уверен, есть ли какие-то большие, широкие исследования по компромиссам, но лучшее из известных мне, которое я очень рекомендую, это Создание быстрого карри: Push / Enter против Eval / Подать заявку на языки высшего порядка Саймон Марлоу и Саймон Пейтон Джонс. Одна из многих замечательных особенностей этой статьи заключается в том, что она объясняет, почему тип функции не сообщает вам, является ли вызов этой функции полностью насыщенным.
Чтобы подвести итоги пронумерованных альтернатив: номер 1 не является стартером.
Популярные компиляторы используют гибридную стратегию, связанную с номерами 2 и 3.
Я никогда не слышал о чем-то похожем на номер 4; Различие между известными и неизвестными вызовами кажется более полезным, чем разграничение функций верхнего уровня от аргументов типа функции.