programming languages programming-languages haskell functional-programming wolfram-mathematica

programming-languages - programming languages 2018



Mathematica: ¿qué es la programación simbólica? (4)

Como otros mencionaron aquí, Mathematica hace una gran cantidad de reescritura de términos. Sin embargo, tal vez Haskell no sea la mejor comparación, pero Pure es un buen lenguaje funcional de reescritura de términos (que debería resultarle familiar a las personas con antecedentes de Haskell). Tal vez leer su página Wiki sobre la reescritura de términos borre algunas cosas para usted:

http://code.google.com/p/pure-lang/wiki/Rewriting

Soy un gran admirador de Stephen Wolfram, pero definitivamente no es tímido para tocar su propio cuerno. En muchas referencias, elogia a Mathematica como un paradigma de programación simbólica diferente. No soy un usuario de Mathematica.

Mis preguntas son: ¿qué es esta programación simbólica? ¿Y cómo se compara con los lenguajes funcionales (como Haskell)?


Cuando escucho la frase "programación simbólica", inmediatamente saltan a la mente LISP, Prolog y (sí) Mathematica. Yo caracterizaría un entorno de programación simbólico como uno en el cual las expresiones utilizadas para representar el texto del programa también resultan ser la estructura de datos primaria. Como resultado, se vuelve muy fácil construir abstracciones sobre abstracciones ya que los datos pueden transformarse fácilmente en código y viceversa.

Mathematica explota esta capacidad en gran medida. Incluso más que LISP y Prolog (en mi humilde opinión).

Como ejemplo de programación simbólica, considere la siguiente secuencia de eventos. Tengo un archivo CSV que se ve así:

r,1,2 g,3,4

Leí ese archivo en:

Import["somefile.csv"] --> {{r,1,2},{g,3,4}}

Es el resultado de datos o código? Son ambos. Son los datos que resultan de leer el archivo, pero también es la expresión que construirá esos datos. Sin embargo, como va el código, esta expresión es inerte ya que el resultado de evaluarlo es simplemente él mismo.

Entonces ahora aplico una transformación al resultado:

% /. {c_, x_, y_} :> {c, Disk[{x, y}]} --> {{r,Disk[{1,2}]},{g,Disk[{3,4}]}}

Sin pensar en los detalles, todo lo que sucedió es que el Disk[{...}] se ha ajustado a los dos últimos números de cada línea de entrada. El resultado sigue siendo datos / código, pero sigue siendo inerte. Otra transformación:

% /. {"r" -> Red, "g" -> Green} --> {{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}

Sí, todavía inerte. Sin embargo, por una notable coincidencia, este último resultado resulta ser una lista de directivas válidas en el lenguaje de gráficos incorporado de dominio específico de Mathematica. Una última transformación, y las cosas comienzan a suceder:

% /. x_ :> Graphics[x] --> Graphics[{{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}]

En realidad, no verías ese último resultado. En una muestra épica de azúcar sintáctica, Mathematica mostraría esta imagen de círculos rojos y verdes:

Pero la diversión no se detiene allí. Debajo de todo ese azúcar sintáctico todavía tenemos una expresión simbólica. Puedo aplicar otra regla de transformación:

% /. Red -> Black

¡Presto! El círculo rojo se volvió negro.

Es este tipo de "empuje simbólico" lo que caracteriza la programación simbólica. La gran mayoría de la programación de Mathematica es de esta naturaleza.

Funcional vs. Simbólico

No abordaré en detalle las diferencias entre programación simbólica y funcional, pero aportaré algunas observaciones.

Uno podría ver la programación simbólica como una respuesta a la pregunta: "¿Qué pasaría si traté de modelar todo utilizando solo transformaciones de expresión?" La programación funcional, por el contrario, puede verse como una respuesta a: "¿Qué pasaría si traté de modelar todo usando solo funciones?" Al igual que la programación simbólica, la programación funcional facilita la creación rápida de capas de abstracciones. El ejemplo que presenté aquí podría reproducirse fácilmente en, por ejemplo, Haskell utilizando un enfoque de animación reactiva funcional. La programación funcional tiene que ver con la composición de la función, las funciones de nivel superior, los combinadores, todas las cosas ingeniosas que se pueden hacer con las funciones.

Mathematica está claramente optimizado para la programación simbólica. Es posible escribir código en estilo funcional, pero las características funcionales de Mathematica son en realidad un barniz delgado sobre las transformaciones (y una abstracción con goteras, vea la nota a pie de página a continuación).

Haskell está claramente optimizado para la programación funcional. Es posible escribir código en estilo simbólico, pero me gustaría objetar que la representación sintáctica de los programas y los datos son bastante distintos, por lo que la experiencia es subóptima.

Observaciones finales

En conclusión, defiendo que hay una distinción entre la programación funcional (como lo resume Haskell) y la programación simbólica (como lo resume Mathematica). Creo que si uno estudia ambos, aprenderá mucho más que estudiar solo uno: la prueba definitiva de la distinción.

Leaky Functional Abstraction en Mathematica?

Sí, goteando. Prueba esto, por ejemplo:

f[x_] := g[Function[a, x]]; g[fn_] := Module[{h}, h[a_] := fn[a]; h[0]]; f[999]

Debidamente informado, y reconocido por, WRI. La respuesta: evite el uso de la Function[var, body] (la Function[body] está bien).


Mathematica está utilizando la reescritura de términos en gran medida. El lenguaje proporciona sintaxis especial para varias formas de reescritura, soporte especial para reglas y estrategias. El paradigma no es tan "nuevo" y, por supuesto, no es único, pero definitivamente están al borde de la "programación simbólica", junto con otros jugadores fuertes como Axiom.

En cuanto a la comparación con Haskell, bueno, podrías hacer la reescritura allí, con un poco de ayuda al eliminar tu biblioteca repetitiva, pero no es tan fácil como en una Mathematica de tipeo dinámico.


Puede pensar en la programación simbólica de Mathematica como un sistema de búsqueda y reemplazo donde programa especificando reglas de búsqueda y reemplazo.

Por ejemplo, podría especificar la siguiente regla

area := Pi*radius^2;

La próxima vez que use el area , será reemplazada por un Pi*radius^2 . Ahora, supongamos que defines una nueva regla

radius:=5

Ahora, cada vez que use el radius , se volverá a escribir en 5 . Si evalúa el area , se reescribirá en Pi*radius^2 que activará la regla de reescritura para el radius y obtendrá Pi*5^2 como resultado intermedio. Esta nueva forma activará una regla de reescritura incorporada para ^ operation, por lo que la expresión se volverá a escribir en Pi*25 . En este punto, la reescritura se detiene porque no hay reglas aplicables.

Puede emular la programación funcional utilizando sus reglas de reemplazo como función. Por ejemplo, si quiere definir una función que agregue, podría hacer

add[a_,b_]:=a+b

Ahora add[x,y] se reescribe en x+y . Si quieres agregar para aplicar solo para numérico a, b, podrías hacer

add[a_?NumericQ, b_?NumericQ] := a + b

Ahora, add[2,3] se reescribe en 2+3 usando su regla y luego en 5 usando la regla incorporada para + , mientras que add[test1,test2] permanece sin cambios.

Aquí hay un ejemplo de una regla de reemplazo interactiva

a := ChoiceDialog["Pick one", {1, 2, 3, 4}] a+1

Aquí, a se reemplaza con ChoiceDialog , que luego se reemplaza con el número que el usuario eligió en el cuadro de diálogo que apareció, lo que hace que ambas cantidades sean numéricas y activa la regla de reemplazo para + . Aquí, ChoiceDialog como una regla de reemplazo incorporada en la línea de "reemplazar ChoiceDialog [algunas cosas] con el valor del botón en el que el usuario hizo clic".

Las reglas se pueden definir usando condiciones que ellos mismos necesitan pasar por la reescritura de reglas para producir True o False . Por ejemplo, supongamos que inventó un nuevo método de resolución de ecuaciones, pero cree que solo funciona cuando el resultado final de su método es positivo. Usted podría hacer la siguiente regla

solve[x + 5 == b_] := (result = b - 5; result /; result > 0)

Aquí, solve[x+5==20] se reemplaza por 15, pero la solve[x + 5 == -20] no cambia porque no hay una regla que se aplique. La condición que impide que se aplique esta regla es /;result>0 . El evaluador esencialmente busca el resultado potencial de la aplicación de la regla para decidir si continuar con ella.

El evaluador de Mathematica vuelve a escribir con avidez cada patrón con una de las reglas que se aplican a ese símbolo. A veces quieres tener un control más fino, y en ese caso puedes definir tus propias reglas y aplicarlas manualmente como este

myrules={area->Pi radius^2,radius->5} area//.myrules

Esto aplicará las reglas definidas en myrules hasta que el resultado deje de cambiar. Esto es bastante similar al evaluador predeterminado, pero ahora podría tener varios conjuntos de reglas y aplicarlas de manera selectiva. Un example más avanzado muestra cómo crear un evaluador similar a Prolog que busca secuencias de aplicaciones de reglas.

Una desventaja de la versión actual de Mathematica surge cuando necesita usar el evaluador predeterminado de Mathematica (para hacer uso de Integrate , Solve , etc.) y desea cambiar la secuencia predeterminada de evaluación. Eso es posible pero complicated , y me gusta pensar que una implementación futura de la programación simbólica tendrá una forma más elegante de controlar la secuencia de evaluación.