Я думал о том, что я упустил бы при переносе некоторого кода Python на статически типизированный язык, такой как F # или Scala; библиотеки могут быть заменены, краткость сопоставима, но у меня есть много кода на Python, который выглядит следующим образом:
@specialclass
class Thing(object):
@specialFunc
def method1(arg1, arg2):
...
@specialFunc
def method2(arg3, arg4, arg5):
...
Там, где декораторы делают огромное количество: заменяя методы вызываемыми объектами на состояние, дополняя класс дополнительными данными и свойствами и т. Д. Хотя Python позволяет динамическое метапрограммирование обезьяньего патча где угодно, когда угодно, кем угодно, я нахожу, что по сути, все мое метапрограммирование выполняется на отдельной «фазе» программы. i.e.:
load/compile .py files
transform using decorators
// maybe transform a few more times using decorators
execute code // no more transformations!
Эти фазы в основном совершенно разные; Я не запускаю никакого кода прикладного уровня в декораторах и не выполняю никаких функций ниндзя replace-class-with-other-class или replace-function-with-other-function в основном коде приложения. Хотя «динамическая» сущность языка говорит о том, что я могу делать это где угодно, я никогда не берусь заменять функции или переопределять классы в основном коде приложения, потому что он очень быстро сходит с ума.
Я, по сути, выполняю одну перекомпиляцию кода перед тем, как начать его выполнение.
Единственное похожее метапрограммирование, о котором я знаю в статически типизированных языках, - это рефлексия: т.е. получение функций / классов из строк, вызов методов с использованием массивов аргументов и т. Д. Однако это в основном преобразует статически типизированный язык в динамически типизированный язык, теряя все Тип безопасности (поправьте меня, если я не прав?). В идеале, я думаю, у меня было бы что-то вроде следующего:
load/parse application files
load/compile transformer
transform application files using transformer
compile
execute code
По сути, вы будете дополнять процесс компиляции произвольным кодом, скомпилированным с использованием обычного компилятора, который будет выполнять преобразования основного кода приложения. Дело в том, что он по сути эмулирует рабочий процесс «загрузить, преобразовать (и), выполнить), строго соблюдая при этом безопасность типов.
Если код приложения обрабатывается, компилятор будет жаловаться, если код преобразователя обрабатывается, компилятор будет жаловаться, если код преобразователя компилируется, но не делает правильных действий, либо произойдет сбой, либо шаг компиляции после того, как будет жаловаться что окончательные типы не складываются. В любом случае, вы никогда не получите возможные ошибки типа во время выполнения, используя отражение для выполнения динамической диспетчеризации: все это будет статически проверяться на каждом шаге.
Итак, мой вопрос: возможно ли это? Это уже было сделано на каком-то языке или структуре, о которых я не знаю? Это теоретически невозможно? Я не очень знаком с компилятором или теорией формального языка, я знаю, что это сделает этап компиляции завершенным и без гарантии завершения, но мне кажется, что это то, что мне нужно, чтобы соответствовать виду удобного кода. преобразование я получаю в динамическом языке, сохраняя статическую проверку типов.
РЕДАКТИРОВАТЬ: Одним из примеров использования будет полностью универсальный декоратор кэширования. В питоне это будет:
cacheDict = {}
def cache(func):
@functools.wraps(func)
def wrapped(*args, **kwargs):
cachekey = hash((args, kwargs))
if cachekey not in cacheDict.keys():
cacheDict[cachekey] = func(*args, **kwargs)
return cacheDict[cachekey]
return wrapped
@cache
def expensivepurefunction(arg1, arg2):
# do stuff
return result
Хотя функции высшего порядка могут выполнять некоторые из этих функций, а объекты с внутренними функциями могут выполнять некоторые из них, AFAIK не может быть обобщен для работы с любой функцией, принимающей произвольный набор параметров и возвращающей произвольный тип при сохранении типа безопасность. Я мог бы сделать что-то вроде:
public Thingy wrap(Object O){ //this probably won't compile, but you get the idea
return (params Object[] args) => {
//check cache
return InvokeWithReflection(O, args)
}
}
Но все литья полностью убивают безопасность типа.
РЕДАКТИРОВАТЬ: Это простой пример, где сигнатура функции не меняется. В идеале то, что я ищу, могло бы изменить сигнатуру функции, изменив входные параметры или тип вывода (например, состав функции), сохраняя при этом проверку типа.