diff --git a/etuples/dispatch.py b/etuples/dispatch.py index c17a93c..66f9504 100644 --- a/etuples/dispatch.py +++ b/etuples/dispatch.py @@ -7,18 +7,39 @@ from .core import etuple, ExpressionTuple try: - from unification.core import _reify, _unify + from unification.core import _reify, _unify, isvar except ModuleNotFoundError: pass else: def _unify_ExpressionTuple(u, v, s): - return _unify(u._tuple, v._tuple, s) + return _unify(getattr(u, "_tuple", u), getattr(v, "_tuple", v), s) _unify.add((ExpressionTuple, ExpressionTuple, Mapping), _unify_ExpressionTuple) + _unify.add((tuple, ExpressionTuple, Mapping), _unify_ExpressionTuple) + _unify.add((ExpressionTuple, tuple, Mapping), _unify_ExpressionTuple) def _reify_ExpressionTuple(u, s): - return etuple(*_reify(u._tuple, s)) + # The point of all this: we don't want to lose the expression + # tracking/caching information. + res = _reify(u._tuple, s) + + res_same = tuple( + a == b for a, b in zip(u, res) if not isvar(a) and not isvar(b) + ) + + if len(res_same) == len(u) and all(res_same): + # Everything is equal and there are no logic variables + return u + + if getattr(u, "_parent", None) and all(res_same): + # If we simply swapped-out logic variables, then we don't want to + # lose the parent etuple information. + res = etuple(*res) + res._parent = u._parent + return res + + return etuple(*res) _reify.add((ExpressionTuple, Mapping), _reify_ExpressionTuple) diff --git a/requirements.txt b/requirements.txt index c6525e4..887d45b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -e ./ -unification +logical-unification ipython coveralls pydocstyle>=3.0.0 diff --git a/tests/test_dispatch.py b/tests/test_dispatch.py index 4b77936..877bc3e 100644 --- a/tests/test_dispatch.py +++ b/tests/test_dispatch.py @@ -108,4 +108,45 @@ def test_unification(): res = unify(etuple(add, 1, 2), cons(a_lv, b_lv), {}) assert res == {a_lv: add, b_lv: etuple(1, 2)} - assert reify(cons(a_lv, b_lv), res) == etuple(add, 1, 2) + res = reify(cons(a_lv, b_lv), res) + assert isinstance(res, ExpressionTuple) + assert res == etuple(add, 1, 2) + + et = etuple(a_lv,) + res = reify(et, {a_lv: 1}) + assert isinstance(res, ExpressionTuple) + + et = etuple(a_lv,) + # We choose to allow unification with regular tuples. + if etuple(1) == (1,): + res = unify(et, (1,)) + assert res == {a_lv: 1} + + et = etuple(add, 1, 2) + assert et.eval_obj == 3 + + res = unify(et, cons(a_lv, b_lv)) + assert res == {a_lv: add, b_lv: et[1:]} + + # Make sure we've preserved the original object after deconstruction via + # `unify` + assert res[b_lv]._parent is et + assert ((res[a_lv],) + res[b_lv])._eval_obj == 3 + + # Make sure we've preserved the original object after reconstruction via + # `reify` + rf_res = reify(cons(a_lv, b_lv), res) + assert rf_res is et + + et_lv = etuple(add, a_lv, 2) + assert reify(et_lv[1:], {})._parent is et_lv + + # Reify a logic variable to another logic variable + assert reify(et_lv[1:], {a_lv: b_lv})._parent is et_lv + + # TODO: We could propagate the parent etuple when a sub-etuple is `cons`ed + # with logic variables. + # et_1 = et[2:] + # et_2 = cons(b_lv, et_1) + # assert et_2._parent is et + # assert reify(et_2, {a_lv: 1})._parent is et