Спираючись на відповідь Чарльза, основна складність теорії мов програмування полягає в тому, що природне поняття еквівалентності програм, як правило, не є суворою рівністю ні в найпростішій математичній семантиці, яку ви можете дати, ні в базовій машинній моделі. Наприклад, розглянемо наступний біт коду, схожого на 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 займає купу і повертає вам нову купу з новоствореним об'єктом, але і те, що означає програми є інваріантним однаковим при всіх перестановках вхідної купи. Це може включати досить складні математичні структури (наприклад, у цьому випадку робота в категорії, яка забезпечує все, що працює за модулем, у відповідній групі перестановки).