Я думал о том, что я пропущу, перенося код Python на статически типизированный язык, такой как F # или Scala; библиотеки могут быть заменены, краткость сопоставима, но у меня есть много кода на Python, который выглядит следующим образом:
@specialclass
class Thing(object):
@specialFunc
def method1(arg1, arg2):
...
@specialFunc
def method2(arg3, arg4, arg5):
...
В тех случаях, когда декораторы делают огромное количество: заменяя методы вызываемыми объектами на состояние, дополняя класс дополнительными данными и свойствами и т.д. Хотя Python позволяет динамически метапрограммировать обезьяну-патч в любом месте и в любое время кем-либо, я нахожу, что по сути, все мои метапрограммы выполняются в отдельной "фазе" программы. то есть:.
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
По сути, вы должны увеличить процесс компиляции с помощью произвольного кода, скомпилированного с использованием обычного компилятора, который будет выполнять преобразования в главном коде приложения. Дело в том, что он по существу эмулирует "процесс загрузки, преобразования (преобразований), выполняет" рабочий процесс ", строго сохраняя безопасность типов.
Если код приложения запущен, компилятор будет жаловаться, если код трансформатора запущен, компилятор будет жаловаться, если код трансформатора компилируется, но не делает правильную вещь, либо он сбой, либо шаг компиляции после того, как будут жаловаться что конечные типы не складываются. В любом случае вы никогда не получите ошибки типа времени выполнения, используя отражение для динамической отправки: все это будет проверено на каждом шаге.
Итак, мой вопрос: возможно ли это? Это уже сделано на каком-то языке или в рамках, о которых я не знаю? Теоретически это невозможно? Я не очень хорошо знаком с теорией компилятора или формального языка, я знаю, что это сделает завершающий шаг компиляции без гарантии прекращения, но мне кажется, что это то, что мне нужно, чтобы соответствовать типу удобного кода, преобразование я получаю на динамическом языке, сохраняя при этом проверку статического типа.
РЕДАКТИРОВАТЬ: Один пример использования - это полностью общий декодер кеширования. В python это будет:
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
В то время как функции более высокого порядка могут выполнять некоторые из этих или objects-with-functions-inside, могут сделать некоторые из них, 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)
}
}
Но все литье полностью убивает безопасность типов.
EDIT: Это простой пример, когда подпись функции не изменяется. В идеале то, что я искал, могло бы изменить сигнатуру функции, изменив входные параметры или тип вывода (состав функции a.l.a.), сохраняя при этом проверку типов.