programming language iswim fsharp python scala f# metaprogramming

python - language - ¿Metaprogramación estáticamente mecanografiada?



iswim programming language (5)

Entonces mi pregunta es, ¿es esto posible?

Hay muchas maneras de lograr el mismo efecto en lenguajes de programación estáticos.

Básicamente, ha descrito el proceso de reescritura de un término en un programa antes de ejecutarlo. Esta funcionalidad es quizás más conocida en la forma de la macro Lisp, pero algunos lenguajes con tipos estáticos también tienen sistemas de macros, especialmente el sistema de macros camlp4 de OCaml que se puede usar para extender el lenguaje.

Más generalmente, estás describiendo una forma de extensibilidad de lenguaje. Hay muchas alternativas y diferentes idiomas proporcionan diferentes técnicas. Ver la publicación de mi blog Extensibilidad en Programación Funcional para más información. Tenga en cuenta que muchos de estos lenguajes son proyectos de investigación, por lo que la motivación es agregar características novedosas y no necesariamente buenas, por lo que rara vez se actualizan las buenas características que se inventaron en otros lugares.

La familia de lenguajes ML (lenguaje meta) que incluye Estándar ML, OCaml y F # fueron diseñados específicamente para la metaprogramación. En consecuencia, tienden a tener un soporte increíble para leer, analizar, reescribir, interpretar y compilar. Sin embargo, F # es el miembro más alejado de esta familia y carece de herramientas maduras de las que se benefician lenguajes como OCaml (por ejemplo, camlp4, ocamllex, dypgen, menhir, etc.). F # tiene una implementación parcial de fslex, fsyacc y una biblioteca combinadora de analizadores inspirada en Haskell llamada FParsec .

Es posible que encuentre que el problema al que se enfrenta (que no ha descrito) se resuelve mejor utilizando formas más tradicionales de metaprogramación, en particular una DSL o EDSL.

He estado pensando en lo que echaría de menos en la transferencia de un código Python a un lenguaje de tipo estático como F # o Scala; Las bibliotecas pueden ser sustituidas, la concisión es comparable, pero tengo muchos códigos de Python, que son los siguientes:

@specialclass class Thing(object): @specialFunc def method1(arg1, arg2): ... @specialFunc def method2(arg3, arg4, arg5): ...

Donde los decoradores hacen una gran cantidad: reemplazar los métodos con objetos que se pueden llamar con el estado, aumentar la clase con datos y propiedades adicionales, etc. Aunque Python permite la metaprogramación dinámica de parches en cualquier lugar, en cualquier momento, por cualquier persona, creo que es esencialmente todo lo que necesito. La metaprogramación se realiza en una "fase" separada del programa. es decir:

load/compile .py files transform using decorators // maybe transform a few more times using decorators execute code // no more transformations!

Estas fases son básicamente distintas; No ejecuto ningún código de nivel de aplicación en los decoradores, ni realizo ningún ninja reemplazar clase con otra clase o reemplazar función con otra función en el código principal de la aplicación. Aunque la "dinámica" del lenguaje dice que puedo hacerlo en cualquier lugar que quiera, nunca sustituyo las funciones ni redefiní las clases en el código principal de la aplicación porque se vuelve loco muy rápidamente.

Básicamente, estoy realizando una única compilación en el código antes de comenzar a ejecutarlo.

La única metapogramación similar que conozco en lenguajes de tipo estático es la reflexión: es decir, obtener funciones / clases a partir de cadenas, invocar métodos utilizando matrices de argumentos, etc. Sin embargo, esto básicamente convierte el lenguaje de tipo estático en un lenguaje de tipo dinámico, perdiendo todo tipo de seguridad ( ¿corrígeme si me equivoco?). Idealmente, creo, tendría algo como lo siguiente:

load/parse application files load/compile transformer transform application files using transformer compile execute code

Esencialmente, estarías aumentando el proceso de compilación con código arbitrario, compilado usando el compilador normal, que realizará transformaciones en el código principal de la aplicación. El punto es que esencialmente emula el flujo de trabajo "cargar, transformar (es), ejecutar" mientras mantiene estrictamente la seguridad de tipos.

Si el código de la aplicación es borked, el compilador se quejará, si el código del transformador se borked el compilador se quejará, si el código del transformador compila pero no hace lo correcto, se bloqueará o el paso de compilación después se quejará de que los tipos no se suman. En cualquier caso, nunca obtendrá los posibles errores de tipo de tiempo de ejecución utilizando la reflexión para realizar un envío dinámico: todo se verificará de forma estática en cada paso.

Entonces mi pregunta es, ¿es esto posible? ¿Ya se ha hecho en algún lenguaje o marco que no conozco? ¿Es teóricamente imposible? No estoy muy familiarizado con el compilador o la teoría del lenguaje formal, sé que completaría el proceso de compilación y no tendría garantía de finalización, pero me parece que esto es lo que necesitaría para coincidir con el tipo de código conveniente. Transformación que obtengo en un lenguaje dinámico mientras mantengo la verificación de tipos estática.

EDITAR: Un ejemplo de caso de uso sería un decorador de almacenamiento en caché completamente genérico. En python sería:

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

Mientras que las funciones de orden superior pueden hacer algo de esto o los objetos con funciones internas pueden hacer algo de esto, AFAIK no pueden generalizarse para trabajar con ninguna función tomando un conjunto arbitrario de parámetros y devolviendo un tipo arbitrario mientras mantiene la seguridad de tipo. Yo podría hacer cosas como:

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) } }

Pero todo el casting mata por completo el tipo de seguridad.

EDITAR: Este es un ejemplo simple, donde la firma de la función no cambia. Idealmente, lo que estoy buscando podría modificar la firma de la función, cambiar los parámetros de entrada o el tipo de salida (composición de la función ala) mientras se mantiene la verificación de tipo.


Idealmente, lo que estoy buscando podría modificar la firma de la función, cambiar los parámetros de entrada o el tipo de salida (composición de la función ala) mientras se mantiene la verificación de tipo.

Tengo la misma necesidad de hacer que las API R estén disponibles en el mundo de tipo seguro. De esta manera, llevaríamos la riqueza del código científico de R al mundo seguro (tipo) de Scala.

Razón fundamental

  1. Posibilite la documentación de los aspectos de dominio de negocio de las API a través de Specs2 (consulte https://etorreborre.github.io/specs2/guide/SPECS2-3.0/org.specs2.guide.UserGuide.html ; se genera a partir del código Scala). Piensa en el diseño impulsado por dominio aplicado al revés.

  2. Adopte un enfoque orientado al lenguaje para los desafíos que enfrenta SparkR, que trata de combinar Spark con R.

Consulte https://spark-summit.org/east-2015/functionality-and-performance-improvement-of-sparkr-and-its-application/ para intentar mejorar la forma en que se realiza actualmente en SparkR. Vea también https://github.com/onetapbeyond/renjin-spark-executor para una forma simplista de integración.

En cuanto a la solución de esto, podríamos usar Renjin (intérprete basado en Java) como motor de tiempo de ejecución, pero usar StrategoXT Metaborg para analizar R y generar API de Scala fuertemente tipadas (como usted describe).

StrategoTX ( http://www.metaborg.org/en/latest/ ) es la plataforma de desarrollo DSL más poderosa que conozco. Permite combinar / incrustar idiomas utilizando una tecnología de análisis que permite componer idiomas (historia más larga).


Es posible que le interesen los sistemas de transformación de programa fuente a fuente (PTS) .

Dichas herramientas analizan el código fuente, producen un AST, y luego permiten que uno defina análisis y / o transformaciones arbitrarias en el código, y finalmente se regenera el código fuente del AST modificado.

Algunas herramientas proporcionan análisis, construcción de árboles y navegación AST mediante una interfaz de procedimiento, como ANTLR . Muchos de los lenguajes dinámicos más modernos (Python, Scala, etc.) han construido algunas bibliotecas de analizadores de hosting propio, e incluso Java (complementos del compilador) y C # (compilador abierto) están captando esta idea.

Pero en su mayoría estas herramientas solo proporcionan acceso de procedimiento al AST. Un sistema con reescritura de sintaxis de superficie le permite expresar "si ve que esto cambia a eso " utilizando patrones con la sintaxis de los idiomas que se están manipulando. Estos incluyen Stratego / XT y TXL .

Según nuestra experiencia, la manipulación de lenguajes complejos requiere soporte y razonamiento complejos del compilador; Esta es la lección canónica de 70 años de personas que construyen compiladores. Todas las herramientas anteriores sufren por no tener acceso a las tablas de símbolos y diversos tipos de análisis de flujo; Después de todo, la forma en que opera una parte del programa depende de la acción tomada en partes remotas, por lo que el flujo de información es fundamental. [Como se señaló en los comentarios sobre otra respuesta, puede implementar tablas de símbolos / análisis de flujo con esas herramientas; Lo que quiero decir es que no le brindan un soporte especial para hacerlo, y estas son tareas difíciles, aún peor en los lenguajes modernos con sistemas de tipo complejo y flujos de control].

Nuestro kit de herramientas de reingeniería de software DMS es un PTS que proporciona todas las facilidades anteriores ( Life After Parsing ), a un costo al configurarlo para su idioma particular o DSL, que intentamos mejorar al proporcionarlo de forma estándar para los idiomas principales. . [DMS proporciona una infraestructura explícita para crear / administrar tablas de símbolos, control y flujo de datos; esto se ha utilizado para implementar estos mecanismos para Java 1.8 y Full C ++ 14].

DMS también se ha utilizado para definir meta-AOP , herramientas que permiten construir sistemas AOP para lenguajes arbitrarios y aplicar operaciones similares a AOP.

En cualquier caso, en la medida en que simplemente modifique el AST, directa o indirectamente, no tiene garantía de "seguridad de tipo". Solo puedes obtener eso escribiendo reglas de transformación que no lo rompan. Para eso, necesitaría un analizador de teoremas para verificar que cada modificación (o composición de los mismos) no rompió la seguridad de los tipos, y eso es mucho más allá del estado de la técnica. Sin embargo, puede tener cuidado al escribir sus reglas y obtener sistemas bastante útiles.

Puede ver un ejemplo de especificación de un DSL y manipulación con reglas de reescritura de fuente a fuente de sintaxis de superficie, que conserva la semántica, en este ejemplo que define y manipula el álgebra y el cálculo utilizando DMS. Observo que este ejemplo es simple para hacerlo comprensible; en particular, no presenta ninguna de las máquinas de análisis de flujo que ofrece DMS.


Pregunta muy interesante.

Algunos puntos con respecto a la metaprogramación en Scala:

  • En la escala 2.10 habrá desarrollos en la reflexión de la escala.

  • Hay trabajo en la transformación de fuente a fuente (macros) que es algo que está buscando: scalamacros.org

  • Java tiene introspección (a través de la api de reflexión) pero no permite la auto modificación. Sin embargo, puede utilizar herramientas para admitir esto (como javassist ). En teoría, podría usar estas herramientas en Scala para lograr más que la introspección.

  • Por lo que pude entender de su proceso de desarrollo, usted separa su código de dominio de sus decoradores (o una preocupación transversal si lo desea), lo que le permite lograr la modularidad y la simplicidad del código. Esto puede ser un buen uso para la programación orientada a aspectos, que permite solo eso. Para Java hay una biblioteca ( aspectJ ), sin embargo tengo dudas de que se ejecutará con Scala.


Sin saber por qué está haciendo esto, es difícil saber si este tipo de enfoque es el correcto en Scala o F #. Pero ignorando eso por ahora, es posible lograrlo en Scala, al menos, aunque no en el nivel del idioma.

Un plugin de compilador le da acceso al árbol y le permite realizar todo tipo de manipulación de ese árbol, todo verificado completamente.

Hay algunos issues con la generación de métodos sintéticos en los complementos del compilador de Scala. Es difícil para mí saber si eso será un problema para usted.

Es posible solucionar esto creando un complemento de compilador que genere código fuente que luego se compila en un pase separado. Así es como funciona ScalaMock , por ejemplo.