Унификацию помните?
2025-11-19 01:12Представьте что у нас имеется два выражения, состоящие их переменных и констант. Ну или два дерева, ведь выражения однозначно представляются деревьями. И нам хочется сравнить эти два выражения или дерева. Это одно и тоже или разные вещи? В том смысле, что при каких-то значениях переменных выражения совпадают.
Такой алгоритм сопоставления назвали унификацией. Когда-то на нём строили экспертные системы: помните язык Пролог? Алгоритм унификации пытается сделать два символических выражения равными, вычисляя объединяющую подстановку для этих выражений. Подстановка — это функция, заменяющая переменные другими выражениями. Очень важно, что она действует одинаково на все вхождения одной и той же переменной: если подстановка меняет одно вхождение переменной x на a, она должна заменить все вхождения x на a.
Объединяющая подстановка (или унификатор) для двух выражений e1 и e2 — это подстановка σ, такая что применение σ делает e1 и e2 структурно одинаковыми. Рассмотрим на примерах.
Пример 1: простая унификация. Выражения f(x) и f(y) можно унифицировать, заменив y на x (или наоборот).
Тогда унификатор σ действует так:
Пример 2: неудачная унификация. Выражения x + 1 и y + 2 нельзя унифицировать. Не стоит пытаться подставить 3 вместо x и 2 вместо y, чтобы оба выражения стали равны 4. Унификация требует символического равенства, а 1 не сопоставляется с 2.
Пример 3: несколько возможных унификаторов. Для выражений f(x, y) и f(1, y) возможны разные унификаторы:
Алгоритмы унификации обычно стремятся получить наиболее общий унификатор (most general unifier, MGU). Это когда делаются только необходимые подстановки. Все остальные унификаторы получаются из MGU путём добавления новых замен. В примере выше σ₁ — MGU, а σ₂ — его частный случай.
Красивая реализация унификации приведена как пример в книжке The Scheme Programming Language: Section 12.10. A Unification Algorithm. Меньше сотни строчек с комментариями, но понять непросто: unify.ss.
А давайте перепишем на смешной язык Gisp. Который внутри тот же Scheme, но с синтаксисом Go. Мне кажется, гораздо яснее выходит.
Такой алгоритм сопоставления назвали унификацией. Когда-то на нём строили экспертные системы: помните язык Пролог? Алгоритм унификации пытается сделать два символических выражения равными, вычисляя объединяющую подстановку для этих выражений. Подстановка — это функция, заменяющая переменные другими выражениями. Очень важно, что она действует одинаково на все вхождения одной и той же переменной: если подстановка меняет одно вхождение переменной x на a, она должна заменить все вхождения x на a.
Объединяющая подстановка (или унификатор) для двух выражений e1 и e2 — это подстановка σ, такая что применение σ делает e1 и e2 структурно одинаковыми. Рассмотрим на примерах.
Пример 1: простая унификация. Выражения f(x) и f(y) можно унифицировать, заменив y на x (или наоборот).
Тогда унификатор σ действует так:
σ(y) = xσ оставляет остальные переменные без изменений.
Пример 2: неудачная унификация. Выражения x + 1 и y + 2 нельзя унифицировать. Не стоит пытаться подставить 3 вместо x и 2 вместо y, чтобы оба выражения стали равны 4. Унификация требует символического равенства, а 1 не сопоставляется с 2.
Пример 3: несколько возможных унификаторов. Для выражений f(x, y) и f(1, y) возможны разные унификаторы:
σ₁ = { x ↦ 1 } даёт результат f(1, y).Оснований менять y нет, поэтому σ₁ предпочтительнее.
σ₂ = { x ↦ 1, y ↦ 5 } даёт f(1, 5).
Алгоритмы унификации обычно стремятся получить наиболее общий унификатор (most general unifier, MGU). Это когда делаются только необходимые подстановки. Все остальные унификаторы получаются из MGU путём добавления новых замен. В примере выше σ₁ — MGU, а σ₂ — его частный случай.
Красивая реализация унификации приведена как пример в книжке The Scheme Programming Language: Section 12.10. A Unification Algorithm. Меньше сотни строчек с комментариями, но понять непросто: unify.ss.
А давайте перепишем на смешной язык Gisp. Который внутри тот же Scheme, но с синтаксисом Go. Мне кажется, гораздо яснее выходит.
// occurs? returns true if and only if u occurs in v
func occurs(u, v) {
if !pairp(v) {
return false
}
return occursHelper(u, rest(v))
}
func occursHelper(u, l) {
if !pairp(l) {
return false
}
if eq(u, first(l)) {
return true
}
if occurs(u, first(l)) {
return true
}
return occursHelper(u, rest(l))
}
// sigma returns a new substitution procedure extending s by
// the substitution of u with v
func sigma(u, v, s) {
return func(x) {
return sigmaApply(u, v, s, s(x))
}
}
func sigmaApply(u, v, s, x) {
if symbolp(x) {
if eq(x, u) {
return v
}
return x
}
return cons(first(x), map(func(elem) {
return sigmaApply(u, v, s, elem)
}, rest(x)))
}
// try-subst tries to substitute u for v but may require a
// full unification if (s u) is not a variable, and it may
// fail if it sees that u occurs in v.
func trySubst(u, v, s, ks, kf) {
var uSub = s(u)
if !symbolp(uSub) {
return uni(uSub, v, s, ks, kf)
}
var vSub = s(v)
if eq(uSub, vSub) {
return ks(s)
}
if occurs(uSub, vSub) {
return kf("cycle")
}
return ks(sigma(uSub, vSub, s))
}
// uni attempts to unify u and v with a continuation-passing
// style that returns a substitution to the success argument
// ks or an error message to the failure argument kf. The
// substitution itself is represented by a procedure from
// variables to terms.
func uni(u, v, s, ks, kf) {
if symbolp(u) {
return trySubst(u, v, s, ks, kf)
}
if symbolp(v) {
return trySubst(v, u, s, ks, kf)
}
if eq(first(u), first(v)) && length(u) == length(v) {
return uniHelper(rest(u), rest(v), s, ks, kf)
}
return kf("clash")
}
func uniHelper(u, v, s, ks, kf) {
if nullp(u) {
return ks(s)
}
return uni(first(u), first(v), s, func(newS) {
return uniHelper(rest(u), rest(v), newS, ks, kf)
}, kf)
}
// unify shows one possible interface to uni, where the initial
// substitution is the identity procedure, the initial success
// continuation returns the unified term, and the initial failure
// continuation returns the error message.
func unify(u, v) {
return uni(u, v, func(x) {
return x
}, func(s) {
return s(u)
}, func(msg) {
return msg
})
}
no subject
Date: 2025-11-19 14:16 (UTC)А проверять как? Например, можно ли унифицировать x2/x и просто x? Значения этих выражений совпадают почти всегда.
no subject
Date: 2025-11-19 16:38 (UTC)Вы говорите о сокращении, а тут ещё с делением
Интересно другое
X = f(X)
Унифицируется, но напечатать нельзя, бесконечно
no subject
Date: 2025-11-19 18:20 (UTC)no subject
Date: 2025-11-19 18:23 (UTC)Унификация была объяснена как "хочется сравнить эти два выражения или дерева. Это одно и тоже или разные вещи? В том смысле, что при каких-то значениях переменных выражения совпадают".
x и x+x-x совпадают при всех значениях x, это одно и то же или нет. В чуть более сложном примере выше выражения совпадают при почти всех значениях x.
no subject
Date: 2025-11-19 18:34 (UTC)no subject
Date: 2025-11-19 19:18 (UTC)no subject
Date: 2025-11-19 19:21 (UTC)