Статически типизированное метапрограммирование?

Я думал о том, что я пропущу, перенося код 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 

По сути, вы бы увеличили процесс компиляции с помощью произвольного кода, скомпилированного с использованием обычного компилятора, который будет выполнять преобразования в главном коде приложения. Дело в том, что оно по существу эмулирует «рабочий процесс», «выполнение», «выполнение», строго сохраняя безопасность типов.

Если код приложения запущен, компилятор будет жаловаться, если код трансформатора запущен, компилятор будет жаловаться, если код трансформатора компилируется, но не делает правильную вещь, либо он выйдет из строя, либо шаг компиляции после того, как он пожалуется, что окончательный типы не складываются. В любом случае вы никогда не получите ошибки типа времени выполнения, используя рефлексию для динамической отправки: все это будет статически проверяться на каждом шаге.

Так что мой вопрос: возможно ли это? Это уже сделано на каком-то языке или в рамках, о которых я не знаю? Теоретически это невозможно? Я не очень хорошо знаком с теорией компилятора или формального языка, я знаю, что это сделает завершающий шаг компиляции без гарантии прекращения, но мне кажется, что это то, что мне нужно, чтобы соответствовать типу удобного кода, преобразование я получаю на динамическом языке, сохраняя при этом статическую проверку типов.

EDIT: Один пример использования случае будет полностью общий декодер кеширования. В 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: Это простой пример, когда сигнатура функции не изменяется. В идеале, что я ищу, можно было бы изменить сигнатуру функции, изменив входные параметры или тип вывода (композицию функции ala), сохраняя при этом проверку типов.

  • Какие языки программирования можно использовать на Android Dalvik?
  • Как использовать класс Scala внутри Pyspark
  • Фильтр, основанный на другом RDD в Spark
  • Вызов функции Java / Scala из задачи
  • Как работает функция pyspark mapPartitions?
  • Интерпретация эталона в C, Clojure, Python, Ruby, Scala и других
  • Scala эквивалент python echo server / client example?
  • Каковы преобразования Spark, вызывающие Shuffle?
  • 5 Solutions collect form web for “Статически типизированное метапрограммирование?”

    Очень интересный вопрос.

    Некоторые моменты, касающиеся метапрограммирования в Scala:

    • В scala 2.10 появятся изменения в отражении scala

    • Есть работа в источнике преобразования источника (макросы), который вы ищете: scalamacros.org

    • Java имеет интроспекцию (через отражение api), но не допускает самостоятельной модификации. Однако вы можете использовать инструменты для поддержки этого (например, javassist ). Теоретически вы можете использовать эти инструменты в Scala для достижения большего, чем самоанализ.

    • Из того, что я мог понять о вашем процессе разработки, вы отделите свой код домена от своих декораторов (или, если хотите, перекрестная озабоченность), которые позволяют достичь модульности и простоты кода. Это может быть полезно для аспектно-ориентированного программирования, что позволяет именно это. Для Java theres – библиотека ( aspectJ ), однако я сомневаюсь , что она будет работать с Scala.

    Так что мой вопрос: возможно ли это?

    Существует много способов добиться такого же эффекта в статически типизированных языках программирования.

    Вы, по сути, описали процесс выполнения какой-либо перезаписи терминов в программе перед ее выполнением. Эта функциональность, пожалуй, наиболее известна в виде макроса Lisp, но некоторые статически типизированные языки также имеют макросистемы, в первую очередь OCaml's camlp4 macro, которые могут быть использованы для расширения языка.

    В более общем плане вы описываете одну форму расширяемости языка. Существует много альтернатив, и разные языки предоставляют разные методы. Дополнительную информацию см. В моем блоге « Расширяемость в функциональном программировании» . Обратите внимание, что многие из этих языков являются исследовательскими проектами, поэтому мотивация заключается в добавлении новых функций и не обязательно хороших функций, поэтому они редко модифицируют хорошие функции, которые были изобретены в других местах.

    Семейство языков ML (метаязык), включая Standard ML, OCaml и F #, было специально разработано для метапрограммирования. Следовательно, они, как правило, имеют прекрасную поддержку для лексинга, разбора, переписывания, интерпретации и компиляции. Тем не менее, F # является самым далеким членом этого семейства и не имеет зрелых инструментов, которые могут использовать такие языки, как OCaml (например, camlp4, ocamllex, dypgen, menhir и т. Д.). У F # есть частичная реализация fslex, fsyacc и библиотеки с комбинацией парсеров, основанной на Haskell, называемой FParsec .

    Вы вполне можете найти, что проблема, с которой вы сталкиваетесь (которую вы не описали), лучше решается с использованием более традиционных форм метапрограммирования, в первую очередь DSL или EDSL.

    Не зная, почему вы это делаете, трудно понять, является ли такой подход правильным в Scala или F #. Но, игнорируя это, на данный момент это, безусловно, можно достичь в Scala, хотя и не на уровне языка.

    Плагин-компилятор предоставляет вам доступ к дереву и позволяет выполнять все виды манипуляций с этим деревом, все они полностью проверены.

    Есть некоторые проблемы с генерированием синтетических методов в плагинах компилятора Scala – мне сложно узнать, будет ли это проблемой для вас.

    Это можно обойти, создав плагин компилятора, который генерирует исходный код, который затем скомпилируется в отдельный проход. Так работает ScalaMock , например.

    Вам могут быть интересны системы преобразования исходных программ (PTS) .

    Такие инструменты анализируют исходный код, производя AST, а затем позволяют определять произвольные анализы и / или преобразования в коде, наконец, восстанавливая исходный код из модифицированного AST.

    Некоторые инструменты обеспечивают синтаксический анализ, древовидную структуру и навигацию AST посредством процедурного интерфейса, такого как ANTLR . Многие из более современных динамических языков (Python, Scala и т. Д.) Создали собственные библиотеки парсеров собственного хостинга, и даже идеи Java (компиляторы) и C # (открытый компилятор) улавливают эту идею.

    Но в основном эти инструменты обеспечивают только процедурный доступ к AST. Система с переопределением поверхностного синтаксиса позволяет вам выражать «если вы видите это изменение в этом », используя шаблоны с синтаксисом языка (ов), которым манипулируют. К ним относятся Stratego / XT и TXL .

    По нашему опыту, манипулирование сложными языками требует сложной поддержки и рассуждений компилятора; это канонический урок от 70-летнего поколения людей, создающих компиляторы. Все вышеперечисленные инструменты страдают от отсутствия доступа к таблицам символов и анализу различных видов потока; в конце концов, как работает одна часть программы, зависит от действий, предпринятых в отдаленных частях, поэтому поток информации является фундаментальным. [Как отмечено в комментариях к другому ответу, вы можете реализовать таблицы символов / анализ потока с помощью этих инструментов; я говорю, что они не дают вам никакой особой поддержки для этого, и это сложные задачи, что еще хуже на современных языках со сложными типами систем и потоками управления].

    Наш инструментарий DMS Software Reengineering Toolkit – это PTS, который предоставляет все перечисленные выше средства ( Life After Parsing ), при определенной стоимости, при настройке на ваш конкретный язык или DSL, которые мы пытаемся улучшить, предоставив эти готовые для основных языков , [DMS предоставляет явную инфраструктуру для построения / управления таблицами символов, управления и потока данных; это было использовано для реализации этих механизмов для Java 1.8 и полного C ++ 14].

    DMS также используется для определения мета-AOP , инструментов, которые позволяют строить системы AOP для произвольных языков и применять подобные операции AOP.

    В любом случае, если вы просто модифицируете AST, прямо или косвенно, у вас нет гарантии «безопасности типа». Вы можете получить это, написав правила преобразования, которые не нарушают его. Для этого вам понадобится проверка теоремы, чтобы проверить, что каждая модификация (или ее состав) не нарушает безопасность типа, и это в значительной степени выходит за пределы уровня техники. Однако вы можете быть осторожны, как вы пишете свои правила, и получаете довольно полезные системы.

    Вы можете увидеть пример спецификации DSL и манипуляции с правилами перезаписи источника-источника с использованием исходного текста, который сохраняет семантику в этом примере, который определяет и управляет алгеброй и исчислением с использованием DMS. Я отмечаю, что этот пример прост, чтобы сделать его понятным; в частности, в нем нет каких-либо предложений DMS для анализа потока.

    В идеале, что я ищу, можно было бы изменить сигнатуру функции, изменив входные параметры или тип вывода (композицию функции ala), сохраняя при этом проверку типов.

    У меня есть такая же потребность в создании R API-интерфейсов в мире безопасного типа. Таким образом, мы приносим богатство научного кода из R в безопасный мир Scala (типа).

    обоснование

    1. Сделайте возможным документирование аспектов бизнес-домена API через Specs2 (см. https://etorreborre.github.io/specs2/guide/SPECS2-3.0/org.specs2.guide.UserGuide.html ; создается из кода Scala). Think Domain Driven Design применяется обратно.

    2. Возьмите ориентированный на язык подход к задачам, с которыми сталкивается SparkR, который пытается совместить Spark с R.

    См. https://spark-summit.org/east-2015/functionality-and-performance-improvement-of-sparkr-and-its-application/ для попыток улучшить то, как это делается в SparkR. См. Также https://github.com/onetapbeyond/renjin-spark-executor для упрощенного способа интеграции.

    Что касается решения этой проблемы, мы могли бы использовать Renjin (интерпретатор на основе Java) в качестве механизма выполнения, но использовать StrategyoXT Metaborg для анализа R и создания строго типизированных API Scala (как вы описали).

    StrategoTX ( http://www.metaborg.org/en/latest/ ) – самая мощная платформа разработки DSL, которую я знаю. Позволяет комбинировать / внедрять языки, используя технологию синтаксического анализа, которая позволяет составлять языки (более длинный рассказ).

    Interesting Posts
    Python - лучший язык программирования в мире.