Puede pensar en la programación simbólica de Mathematica como un sistema de búsqueda y reemplazo en el que se 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 lo use area
, será reemplazado por Pi*radius^2
. Ahora, suponga que define una nueva regla
radius:=5
Ahora, cada vez que lo use radius
, se reescribirá 5
. Si lo evalúa area
, se reescribirá en Pi*radius^2
qué desencadenadores de la regla de reescritura radius
y obtendrá Pi*5^2
como resultado intermedio. Este nuevo formulario activará una regla de reescritura incorporada para la ^
operación, por lo que la expresión se reescribirá aún más 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 desea definir una función que agregue, puede hacer
add[a_,b_]:=a+b
Ahora add[x,y]
se reescribe en x+y
. Si desea agregar solo para solicitar numérico a, b, en su lugar podría hacer
add[a_?NumericQ, b_?NumericQ] := a + b
Ahora, add[2,3]
se reescribe para 2+3
usar su regla y luego para 5
usar 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 +
. Aquí, ChoiceDialog
como una regla de reemplazo incorporada en la línea de "reemplazar ChoiceDialog [algunas cosas] con el valor del botón que el usuario hizo clic".
Las reglas se pueden definir utilizando condiciones que, a su vez, deben reescribirse para producir True
o False
. Por ejemplo, suponga 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. Podrías hacer la siguiente regla
solve[x + 5 == b_] := (result = b - 5; result /; result > 0)
Aquí, solve[x+5==20]
se reemplaza con 15, pero solve[x + 5 == -20]
no cambia porque no hay ninguna 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 reglas para decidir si seguir adelante con ella.
El evaluador de Mathematica reescribe con avidez cada patrón con una de las reglas que se aplican a ese símbolo. A veces, desea tener un control más preciso y, en tal caso, puede definir sus propias reglas y aplicarlas manualmente de esta manera
myrules={area->Pi radius^2,radius->5}
area
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 forma selectiva. Un ejemplo más avanzado muestra cómo hacer un evaluador similar a Prolog que busque secuencias de aplicaciones de reglas.
Uno de los inconvenientes de la actual versión de Mathematica aparece cuando es necesario utilizar evaluador predeterminado de Mathematica (para hacer uso de Integrate
, Solve
, etc.) y desea la secuencia de cambio predeterminado de evaluación. Eso es posible pero complicado , y me gusta pensar que alguna implementación futura de programación simbólica tendrá una forma más elegante de controlar la secuencia de evaluación.
Solve
otro conjunto de reglas de reescritura. Cuando da algunas ecuaciones que Mathematica no puede resolver,Solve[hard_equations]
quedaSolve[hard_equations]
y puede definir unaSolve
regla personalizada que se aplique en este caso. En este caso, supongo que usan /; condicional para definir un patrón para "cualquier ecuación que se pueda resolver con métodos en Mathematica", por lo que para las ecuaciones rígidas la regla incorporada no se aplica ySolve
permanece en su forma originalCuando escucho la frase "programación simbólica", inmediatamente me vienen a la mente LISP, Prolog y (sí) Mathematica. Caracterizaría un entorno de programación simbólico como uno en el que las expresiones utilizadas para representar el texto del programa también resultan ser la estructura de datos principal. Como resultado, resulta muy fácil construir abstracciones sobre abstracciones, ya que los datos se pueden transformar 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 parece a esto:
Leí ese archivo en:
Import["somefile.csv"] --> {{r,1,2},{g,3,4}}
¿El resultado es un código o datos? Son ambos. Son los datos que resultan de leer el archivo, pero también es la expresión que construirá esos datos. Sin embargo, según el código, esta expresión es inerte ya que el resultado de evaluarla es simplemente ella misma.
Entonces ahora aplico una transformación al resultado:
% /. {c_, x_, y_} :> {c, Disk[{x, y}]} --> {{r,Disk[{1,2}]},{g,Disk[{3,4}]}}
Sin detenerse en los detalles, todo lo que ha sucedido es que
Disk[{...}]
se ha envuelto alrededor de 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 específico de dominio incorporado de Mathematica para gráficos. Una última transformación y empiezan a suceder cosas:
% /. x_ :> Graphics[x] --> Graphics[{{Red,Disk[{1,2}]},{Green,Disk[{3,4}]}}]
En realidad, no verías ese último resultado. En una exhibición épica de azúcar sintáctica, Mathematica mostraría esta imagen de círculos rojos y verdes:
Pero la diversión no termina ahí. Debajo de todo ese azúcar sintáctico todavía tenemos una expresión simbólica. Puedo aplicar otra regla de transformación:
¡Presto! El círculo rojo se volvió negro.
Es este tipo de "empuje de símbolos" lo que caracteriza a 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é las diferencias entre programación simbólica y funcional en detalle, pero contribuiré con algunas observaciones.
Se podría ver la programación simbólica como una respuesta a la pregunta: "¿Qué pasaría si tratara de modelar todo usando solo transformaciones de expresión?" La programación funcional, por el contrario, puede verse como una respuesta a: "¿Qué pasaría si intentara 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 di aquí podría reproducirse fácilmente en, digamos, Haskell utilizando un enfoque de animación reactiva funcional. La programación funcional tiene que ver con la composición de funciones, funciones de nivel superior, combinadores, todas las cosas ingeniosas que puede hacer con funciones.
Mathematica está claramente optimizado para programación simbólica. Es posible escribir código en estilo funcional, pero las características funcionales en Mathematica son en realidad solo una fina capa sobre las transformaciones (y una abstracción con fugas en eso, 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 yo objetaría que la representación sintáctica de los programas y los datos es bastante distinta, lo que hace que la experiencia sea subóptima.
Observaciones finales
En conclusión, defiendo que existe una distinción entre la programación funcional (como la personifica Haskell) y la programación simbólica (como la personifica Mathematica). Creo que si uno estudia ambos, aprenderá sustancialmente más que estudiar solo uno: la prueba definitiva de distinción.
¿Abstracción funcional con fugas en Mathematica?
Sí, con fugas. Pruebe 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 el WRI. La respuesta: evita el uso de
Function[var, body]
(Function[body]
está bien).fuente
Function[var, body]
? Eso es extraño ya que se recomienda en los documentos ...Function[var, body]
. No se ofreció ninguna explicación sobre por qué esto no se pudo arreglar, pero especulo que, dado queFunction
existe desde la versión 1.0, sería desastroso cambiar su comportamiento tan tarde en el juego. El problema se describe en (un poco) más detalle aquí .Function
pueda curarse, incluso en principio, al menos con la semántica intrusa actual deRule
yRuleDelayed
, que no respetan las vinculaciones de las construcciones de alcance interno, incluidos ellos mismos . Este fenómeno me parece más relacionado con esta propiedad deRule
yRuleDelayed
, que específicamente conFunction
. Pero de cualquier manera, estoy de acuerdo en que cambiar esto es muy peligroso ahora. Lástima, porqueFunction[var,body]
no se debe utilizar; estos errores serán casi imposibles de detectar en proyectos importantes.Como otros ya han mencionado aquí, Mathematica hace muchas reescrituras de términos. Sin embargo, quizás Haskell no sea la mejor comparación, pero Pure es un buen lenguaje funcional para reescribir términos (que debería resultar familiar para las personas con experiencia en Haskell). Tal vez la lectura de su página Wiki sobre la reescritura de términos te aclare algunas cosas:
http://code.google.com/p/pure-lang/wiki/Rewriting
fuente
Mathematica está usando mucho la reescritura de términos. El lenguaje proporciona una 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 a la vanguardia de esta "programación simbólica", junto con los otros jugadores fuertes como Axiom.
En cuanto a la comparación con Haskell, bueno, podría reescribir allí, con un poco de ayuda de desechar su biblioteca repetitiva, pero no es tan fácil como en Mathematica tipado dinámicamente.
fuente
Lo simbólico no debe contrastarse con lo funcional, debe contrastarse con la programación numérica. Considere como ejemplo MatLab vs Mathematica. Supongamos que quiero el polinomio característico de una matriz. Si quisiera hacer eso en Mathematica, podría obtener una matriz de identidad (I) y la matriz (A) misma en Mathematica, luego haga esto:
Det[A-lambda*I]
Y obtendría el polinomio característico (no importa que probablemente haya una función polinomial característica), por otro lado, si estuviera en MatLab no podría hacerlo con MatLab base porque MatLab base (no importa que probablemente haya un polinomio característico function) solo es bueno para calcular números de precisión finita, no cosas donde hay lambdas aleatorias (nuestro símbolo) allí. Lo que tendría que hacer es comprar el complemento Symbolab, y luego definir lambda como su propia línea de código y luego escribir esto (en el que convertiría su matriz A en una matriz de números racionales en lugar de decimales de precisión finita) , y aunque la diferencia de rendimiento probablemente sería imperceptible para un caso pequeño como este, probablemente lo haría mucho más lento que Mathematica en términos de velocidad relativa.
Entonces esa es la diferencia, los lenguajes simbólicos están interesados en hacer cálculos con perfecta precisión (a menudo usando números racionales en lugar de numéricos) y los lenguajes de programación numérica, por otro lado, son muy buenos en la gran mayoría de los cálculos que necesitaría hacer y tienden a para ser más rápidos en las operaciones numéricas para las que están destinados (MatLab es casi incomparable en este sentido para lenguajes de nivel superior, excluyendo C ++, etc.) y un piss pobre en operaciones simbólicas.
fuente