Основываясь на ответе Чарльза, основная трудность в теории языков программирования заключается в том, что естественным понятием эквивалентности программ обычно является не строгое равенство ни в самой простой математической семантике, которую вы можете дать, ни в базовой модели машины. Например, рассмотрим следующий бит Java-подобного кода:
Object x = new Object();
Object y = new Object();
... some more code ...
Таким образом, эта программа создает объект и называет его x, а затем создает второй объект с именем y, а затем продолжает выполнять еще немного кода. Теперь предположим, что программист решает изменить порядок размещения этих двух объектов:
Object y = new Object();
Object x = new Object();
... some more code ...
Теперь задайте вопрос: меняет ли этот рефакторинг поведение программы? С одной стороны, на базовом компьютере x и y будут размещены в разных местах в двух запусках программы. Так что в этом смысле программа ведет себя по-разному.
Но в Java-подобном языке вы можете проверять ссылки только на равенство, а не на порядок, так что это различие, которое «еще немного кода» не может наблюдать . В результате большинство программистов ожидают, что изменение порядка не будет иметь никакого значения для окончательного ответа, и большинство авторов компиляторов рассчитывают на возможность переупорядочения и оптимизации на этой основе. (С другой стороны, в C-подобном языке вы можете сравнивать указатели для упорядочения, сначала приводя их к целым числам, и поэтому такое переупорядочение не обязательно сохраняет наблюдаемое поведение.)
Один из центральных вопросов семантики заключается в том, чтобы ответить на вопрос о том, когда две программы заметно различаются. Поскольку наше представление о наблюдении зависит от особенностей языка программирования, в итоге мы получаем определение типа «две программы эквивалентны, когда ни одна клиентская программа не может вычислить разные ответы на основе получения этих программ в качестве входных данных». Количественная оценка всех клиентских программ делает этот вопрос трудным - кажется, что вам приходится говорить что-то обо всех возможных клиентских программах, чтобы сказать что-то о двух конкретных частях кода.
Уловка с денотационной семантикой заключается в том, чтобы дать математическую интерпретацию, которая позволит вам избежать этой универсальной количественной оценки - вы говорите, что смысл фрагмента кода является неким математическим значением, и сравниваете их, проверяя, равны ли они математически или не. Это локальный (то есть составной) вариант, который не включает количественную оценку всех возможных клиентов. (Вам действительно нужно показать, что денотационная семантика подразумевает контекстуальную эквивалентность, чтобы она звучала, конечно. Когда она завершена - когда денотационное равенство точно такое же, как контекстуальная эквивалентность, мы говорим, что семантика «полностью абстрактна».)
Но означает, что вам нужно убедиться, что денотационная семантика проверяет эти эквивалентности. Таким образом, для этого примера, если вы хотите дать денотационную семантику для этого Java-подобного языка, вам нужно убедиться, что вызов new не просто занимает кучу и возвращает новую кучу с вновь созданным объектом, но что значение программы инвариантны одинаково при всех перестановках входной кучи. Это может включать в себя довольно сложные математические структуры (например, в этом случае работа в категории, которая гарантирует, что все работает по модулю подходящей группы перестановок).