Sobre la base de la respuesta de Charles, la principal dificultad en la teoría de los lenguajes de programación es que la noción natural de equivalencia de los programas generalmente no es la igualdad estricta ni en la semántica matemática más directa que puede dar, ni en el modelo de máquina subyacente. Por ejemplo, considere el siguiente bit de código similar a Java:
Object x = new Object();
Object y = new Object();
... some more code ...
Entonces, este programa crea un objeto y lo nombra x, y luego crea un segundo objeto llamado y, y luego continúa ejecutando más código. Ahora, suponga que un programador decide cambiar el orden de asignación de estos dos objetos:
Object y = new Object();
Object x = new Object();
... some more code ...
Ahora, haga la pregunta: ¿esta refactorización cambia el comportamiento del programa? Por un lado, en la máquina subyacente, x e y se asignarán en diferentes ubicaciones en las dos ejecuciones del programa. Entonces, en este sentido, el programa se comporta de manera diferente.
Pero en un lenguaje similar a Java, solo puede probar las referencias para la igualdad, y no para el orden, por lo que esta es una diferencia que el "algún código más" no puede observar . Como resultado, la mayoría de los programadores esperarán que invertir el orden no haga ninguna diferencia en la respuesta final, y la mayoría de los escritores de compiladores esperan poder realizar reordenamientos y optimizaciones sobre esta base. (Por otro lado, en un lenguaje similar a C, puede comparar punteros para ordenar, convirtiéndolos primero en enteros, por lo que este reordenamiento no necesariamente conserva un comportamiento observable).
Una de las preguntas centrales de la semántica es responder a la pregunta de cuándo dos programas son observablemente equivalentes. Dado que nuestra noción de observación depende de las características del lenguaje de programación, terminamos con una definición como "dos programas son equivalentes cuando ningún programa cliente puede calcular diferentes respuestas basadas en recibir esos programas como entradas". La cuantificación de todos los programas cliente es lo que hace que esta pregunta sea difícil: parece que terminas teniendo que decir algo sobre todos los posibles programas cliente para decir algo sobre dos piezas de código en particular.
El truco con la semántica denotacional es dar una interpretación matemática que le permita evitar esta cuantificación universal: usted dice que el significado de un fragmento de código es un valor matemático, y los compara verificando si son matemáticamente iguales o no. Esto es local (es decir, de composición) y no implica la cuantificación de todos los clientes posibles. (Es necesario demostrar que la semántica denotacional implica equivalencia contextual para que sea sólida, por supuesto. Cuando está completa, cuando la igualdad denotacional es exactamente la misma que la equivalencia contextual, decimos que la semántica es "completamente abstracta").
Pero significa que debe asegurarse de que la semántica denotacional valida esas equivalencias. Entonces, para este ejemplo, si desea dar una semántica denotacional para este lenguaje similar a Java, debe asegurarse no solo de que llamar a nuevo toma un montón y le devuelve un nuevo montón con el objeto recién creado, sino que el significado del programa es invariante igual bajo todas las permutaciones del montón de entrada. Esto puede implicar estructuras matemáticas bastante complejas (por ejemplo, en este caso trabajar en una categoría que garantiza que todo funcione en un grupo de permutación adecuado).