Skip to content

Commit

Permalink
Rename ExpressionTuple.eval_obj to evaled_obj
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonwillard committed Jun 25, 2021
1 parent 96abcb8 commit d8586c3
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 57 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ ExpressionTuple((<built-in function add>, 1))
`etuple`s can also be evaluated:

```python
>>> et.eval_obj
>>> et.evaled_obj
3
```

Evaluated `etuple`s are cached:
```python
>>> et = etuple(add, "a", "b")
>>> et.eval_obj
>>> et.evaled_obj
'ab'

>>> et.eval_obj is et.eval_obj
>>> et.evaled_obj is et.evaled_obj
True
```

Expand All @@ -46,7 +46,7 @@ Reconstructed `etuple`s and their evaluation results are preserved across tuple
>>> et_new = (et[0],) + et[1:]
>>> et_new is et
True
>>> et_new.eval_obj is et.eval_obj
>>> et_new.evaled_obj is et.evaled_obj
True
```

Expand Down Expand Up @@ -115,7 +115,7 @@ def apply_Operator(rator, rands):
>>> pprint(et)
e(+, e(*, 1, 2), 3)

>>> et.eval_obj is add_node
>>> et.evaled_obj is add_node
True
```

Expand Down
42 changes: 26 additions & 16 deletions etuples/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import inspect
import reprlib
import warnings
from collections import deque
from collections.abc import Generator, Sequence

Expand Down Expand Up @@ -107,15 +108,15 @@ class ExpressionTuple(Sequence):
TODO: Should probably use weakrefs for that.
"""

__slots__ = ("_eval_obj", "_tuple", "_parent")
__slots__ = ("_evaled_obj", "_tuple", "_parent")
null = object()

def __new__(cls, seq=None, **kwargs):

# XXX: This doesn't actually remove the entry from the kwargs
# passed to __init__!
# It does, however, remove it for the check below.
kwargs.pop("eval_obj", None)
kwargs.pop("evaled_obj", None)

if seq is not None and not kwargs and type(seq) == cls:
return seq
Expand All @@ -127,13 +128,13 @@ def __new__(cls, seq=None, **kwargs):
def __init__(self, seq=None, **kwargs):
"""Create an expression tuple.
If the keyword 'eval_obj' is given, the `ExpressionTuple`'s
If the keyword 'evaled_obj' is given, the `ExpressionTuple`'s
evaluated object is set to the corresponding value.
XXX: There is no verification/check that the arguments evaluate to the
user-specified 'eval_obj', so be careful.
user-specified 'evaled_obj', so be careful.
"""

_eval_obj = kwargs.pop("eval_obj", self.null)
_evaled_obj = kwargs.pop("evaled_obj", self.null)
etuple_kwargs = tuple(KwdPair(k, v) for k, v in kwargs.items())

if seq:
Expand All @@ -142,20 +143,29 @@ def __init__(self, seq=None, **kwargs):
self._tuple = etuple_kwargs

# TODO: Consider making these a weakrefs.
self._eval_obj = _eval_obj
self._evaled_obj = _evaled_obj
self._parent = None

@property
def eval_obj(self):
def evaled_obj(self):
"""Return the evaluation of this expression tuple."""
return trampoline_eval(self._eval_step())

@property
def eval_obj(self):
warnings.warn(
"`eval_obj` is deprecated; use `evaled_obj`.",
DeprecationWarning,
stacklevel=2,
)
return trampoline_eval(self._eval_step())

def _eval_step(self):
if len(self._tuple) == 0:
raise InvalidExpression("Empty expression.")

if self._eval_obj is not self.null:
yield self._eval_obj
if self._evaled_obj is not self.null:
yield self._evaled_obj
else:
op = self._tuple[0]

Expand All @@ -182,22 +192,22 @@ def _eval_step(self):
op_sig = inspect.signature(op)
except ValueError:
# This handles some builtin function types
_eval_obj = op(*(evaled_args + [kw.value for kw in evaled_kwargs]))
_evaled_obj = op(*(evaled_args + [kw.value for kw in evaled_kwargs]))
else:
op_args = op_sig.bind(
*evaled_args, **{kw.arg: kw.value for kw in evaled_kwargs}
)
op_args.apply_defaults()

_eval_obj = op(*op_args.args, **op_args.kwargs)
_evaled_obj = op(*op_args.args, **op_args.kwargs)

# assert not isinstance(_eval_obj, ExpressionTuple)
# assert not isinstance(_evaled_obj, ExpressionTuple)

self._eval_obj = _eval_obj
yield self._eval_obj
self._evaled_obj = _evaled_obj
yield self._evaled_obj

@eval_obj.setter
def eval_obj(self, obj):
@evaled_obj.setter
def evaled_obj(self, obj):
raise ValueError("Value of evaluated expression cannot be set!")

def __add__(self, x):
Expand Down
4 changes: 2 additions & 2 deletions etuples/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def apply_Sequence(rator, rands):

@apply.register(Callable, ExpressionTuple)
def apply_ExpressionTuple(rator, rands):
return ((rator,) + rands).eval_obj
return ((rator,) + rands).evaled_obj


# These are used to maintain some parity with the old `kanren.term` API
Expand Down Expand Up @@ -160,6 +160,6 @@ def etuplize_step(
)
et_args.append(e)

yield etuple(et_op, *et_args, eval_obj=x)
yield etuple(et_op, *et_args, evaled_obj=x)

return trampoline_eval(etuplize_step(x))
61 changes: 32 additions & 29 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,35 @@ def test_ExpressionTuple(capsys):
assert 2 * e0 == ExpressionTuple((add, 1, 2, add, 1, 2))

e1 = ExpressionTuple((add, e0, 3))
assert e1.eval_obj == 6
assert e1.evaled_obj == 6

# ("_eval_obj", "_tuple", "_parent")
# ("_evaled_obj", "_tuple", "_parent")
e2 = e1[1:2]
assert e2._parent is e1

assert e2 == ExpressionTuple((e0,))

ExpressionTuple((print, "hi")).eval_obj
ExpressionTuple((print, "hi")).evaled_obj
captured = capsys.readouterr()
assert captured.out == "hi\n"

e3 = ExpressionTuple(())

with pytest.raises(InvalidExpression):
e3.eval_obj
e3.evaled_obj

e4 = ExpressionTuple((1,))

with pytest.raises(InvalidExpression):
e4.eval_obj
e4.evaled_obj

assert ExpressionTuple((ExpressionTuple((lambda: add,)), 1, 1)).eval_obj == 2
assert ExpressionTuple((ExpressionTuple((lambda: add,)), 1, 1)).evaled_obj == 2
assert ExpressionTuple((1, 2)) != ExpressionTuple((1,))
assert ExpressionTuple((1, 2)) != ExpressionTuple((1, 3))

with pytest.warns(DeprecationWarning):
ExpressionTuple((print, "hi")).eval_obj


def test_etuple():
"""Test basic `etuple` functionality."""
Expand All @@ -72,17 +75,17 @@ def test_op(*args):

e1 = etuple(test_op, 1, 2)

assert e1._eval_obj is ExpressionTuple.null
assert e1._evaled_obj is ExpressionTuple.null

with pytest.raises(ValueError):
e1.eval_obj = 1
e1.evaled_obj = 1

e1_obj = e1.eval_obj
e1_obj = e1.evaled_obj
assert len(e1_obj) == 3
assert all(type(o) == object for o in e1_obj)

# Make sure we don't re-create the cached `eval_obj`
e1_obj_2 = e1.eval_obj
# Make sure we don't re-create the cached `evaled_obj`
e1_obj_2 = e1.evaled_obj
assert e1_obj == e1_obj_2

# Confirm that evaluation is recursive
Expand All @@ -96,12 +99,12 @@ def test_op(*args):
assert isinstance(e2[:1], ExpressionTuple)
assert e2[1] == e2[1:2][0]

e2_obj = e2.eval_obj
e2_obj = e2.evaled_obj

assert type(e2_obj) == tuple
assert len(e2_obj) == 4
assert all(type(o) == object for o in e2_obj)
# Make sure that it used `e1`'s original `eval_obj`
# Make sure that it used `e1`'s original `evaled_obj`
assert e2_obj[1:] == e1_obj

# Confirm that any combination of `tuple`s/`etuple`s in
Expand All @@ -128,50 +131,50 @@ def test_func(a, b, c=None, d="d-arg", **kwargs):
return [a, b, c, d]

e1 = etuple(test_func, 1, 2)
assert e1.eval_obj == [1, 2, None, "d-arg"]
assert e1.evaled_obj == [1, 2, None, "d-arg"]

# Make sure we handle variadic args properly
def test_func2(*args, c=None, d="d-arg", **kwargs):
assert isinstance(c, (type(None), int))
return list(args) + [c, d]

e0 = etuple(test_func2, c=3)
assert e0.eval_obj == [3, "d-arg"]
assert e0.evaled_obj == [3, "d-arg"]

e11 = etuple(test_func2, 1, 2)
assert e11.eval_obj == [1, 2, None, "d-arg"]
assert e11.evaled_obj == [1, 2, None, "d-arg"]

e2 = etuple(test_func, 1, 2, 3)
assert e2.eval_obj == [1, 2, 3, "d-arg"]
assert e2.evaled_obj == [1, 2, 3, "d-arg"]

e3 = etuple(test_func, 1, 2, 3, 4)
assert e3.eval_obj == [1, 2, 3, 4]
assert e3.evaled_obj == [1, 2, 3, 4]

e4 = etuple(test_func, 1, 2, c=3)
assert e4.eval_obj == [1, 2, 3, "d-arg"]
assert e4.evaled_obj == [1, 2, 3, "d-arg"]

e5 = etuple(test_func, 1, 2, d=3)
assert e5.eval_obj == [1, 2, None, 3]
assert e5.evaled_obj == [1, 2, None, 3]

e6 = etuple(test_func, 1, 2, 3, d=4)
assert e6.eval_obj == [1, 2, 3, 4]
assert e6.evaled_obj == [1, 2, 3, 4]

# Try evaluating nested etuples
e7 = etuple(test_func, etuple(add, 1, 0), 2, c=etuple(add, 1, etuple(add, 1, 1)))
assert e7.eval_obj == [1, 2, 3, "d-arg"]
assert e7.evaled_obj == [1, 2, 3, "d-arg"]

# Try a function without an obtainable signature object
e8 = etuple(
enumerate,
etuple(list, ["a", "b", "c", "d"]),
start=etuple(add, 1, etuple(add, 1, 1)),
)
assert list(e8.eval_obj) == [(3, "a"), (4, "b"), (5, "c"), (6, "d")]
assert list(e8.evaled_obj) == [(3, "a"), (4, "b"), (5, "c"), (6, "d")]

# Use "eval_obj" kwarg and make sure it doesn't end up in the `_tuple` object
e9 = etuple(add, 1, 2, eval_obj=3)
# Use "evaled_obj" kwarg and make sure it doesn't end up in the `_tuple` object
e9 = etuple(add, 1, 2, evaled_obj=3)
assert e9._tuple == (add, 1, 2)
assert e9._eval_obj == 3
assert e9._evaled_obj == 3


def test_str():
Expand Down Expand Up @@ -209,18 +212,18 @@ def gen_long_add_chain(N=None, num=1):
def test_reify_recursion_limit():

a = gen_long_add_chain(10)
assert a.eval_obj == 11
assert a.evaled_obj == 11

r_limit = sys.getrecursionlimit()

try:
sys.setrecursionlimit(100)

a = gen_long_add_chain(200)
assert a.eval_obj == 201
assert a.evaled_obj == 201

b = gen_long_add_chain(200, num=2)
assert b.eval_obj == 402
assert b.evaled_obj == 402

c = gen_long_add_chain(200)
assert a == c
Expand Down
10 changes: 5 additions & 5 deletions tests/test_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ def test_etuple_apply():
assert apply(add, (1, 2)) == 3
assert apply(1, (2,)) == (1, 2)

# Make sure that we don't lose underlying `eval_obj`s
# Make sure that we don't lose underlying `evaled_obj`s
# when taking apart and re-creating expression tuples
# using `kanren`'s `operator`, `arguments` and `term`
# functions.
e1 = etuple(add, (object(),), (object(),))
e1_obj = e1.eval_obj
e1_obj = e1.evaled_obj

e1_dup = (rator(e1),) + rands(e1)

assert isinstance(e1_dup, ExpressionTuple)
assert e1_dup.eval_obj == e1_obj
assert e1_dup.evaled_obj == e1_obj

e1_dup_2 = apply(rator(e1), rands(e1))
assert e1_dup_2 == e1_obj
Expand Down Expand Up @@ -128,15 +128,15 @@ def test_unification():
assert res == {a_lv: 1}

et = etuple(add, 1, 2)
assert et.eval_obj == 3
assert et.evaled_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
assert ((res[a_lv],) + res[b_lv])._evaled_obj == 3

# Make sure we've preserved the original object after reconstruction via
# `reify`
Expand Down

0 comments on commit d8586c3

Please sign in to comment.