Я работаю над реализацией обычного алгоритма унификации обычным способом: рекурсивный спуск по деревьям выражений, попутно добавляя привязки переменных к хеш-таблице, выполняя проверку возникновения. В Java, как это бывает, используются функции переопределения для соответствия зерну языка, поэтому часть реализации, имеющая дело с переменными, следующая:
@Override
public boolean unify(Term a, Map<Var, Term> map) {
if (this == a) {
return true;
}
Term x = map.get(this);
if (x != null) {
return x.unify(a, map);
}
if (a instanceof Var) {
x = map.get((Var) a);
if (x != null) {
return x.unify(this, map);
}
}
if (a.occurs(this)) {
return false;
}
map.put(this, a);
return true;
}
Эта версия правильная и для многих случаев довольно быстрая, но у нее есть проблема, которая возникает, в частности, при ее использовании для вывода типов. При объединении множества переменных с одной и той же целью получается набор привязок, который в основном выглядит следующим образом:
a=b
b=c
c=d
d=e
Затем каждый раз, когда новая переменная должна быть объединена с одним и тем же объектом, она должна проходить по цепочке один шаг за раз, чтобы найти, где она находится на данный момент, что занимает время O (N), что означает объединение коллекции переменных для то же самое занимает общее время O (N ^ 2).
Вероятно, лучшее решение - реализовать какой-то ярлык, что-то вроде обновления a
, чтобы он указывал непосредственно на текущую конечную цель, какой бы она ни была. Не совсем очевидно, как это сделать правильно и эффективно во всех случаях.
Унификация была хорошо известна и довольно широко использовалась в течение десятилетий, поэтому я полагаю, что решение этой проблемы также должно было быть известно в течение десятилетий, но несколько обсуждений, которые я видел по поводу объединения, похоже, не упоминают об этом.
Как именно можно изменить алгоритм, чтобы справиться с этим?