|
| 1 | +from itertools import tee |
| 2 | +from typing import Mapping, Optional, Sequence, Tuple, Union |
| 3 | + |
| 4 | +from cons import car, cdr |
| 5 | +from toolz import interleave |
| 6 | +from unification import reify |
| 7 | +from unification.utils import transitive_get as walk |
| 8 | + |
| 9 | +from .core import lall, lconj_seq, ldisj_seq |
| 10 | + |
| 11 | + |
| 12 | +def collect(s: Mapping, f_lists: Optional[Sequence] = None): |
| 13 | + """A function that produces suggestions (for `condp`) based on the values of |
| 14 | + partially reified terms. |
| 15 | +
|
| 16 | + This goal takes a list of suggestion function, variable pairs lists and |
| 17 | + evaluates them at their current, partially reified variable values |
| 18 | + (i.e. ``f(walk(x, s))`` for pair ``(f, x)``). Each evaluated function should |
| 19 | + return ``None``, a string label in a corresponding `condp` clause, or the |
| 20 | + string ``"use-maybe"``. |
| 21 | +
|
| 22 | + Each list of suggestion functions is evaluated in order, their output is |
| 23 | + concatenated, and, if the output contains a ``"use-maybe"`` string, the |
| 24 | + next list of suggestion functions is evaluated. |
| 25 | +
|
| 26 | + Parameters |
| 27 | + ========== |
| 28 | + s |
| 29 | + miniKanren state. |
| 30 | + f_lists |
| 31 | + A collection of function + variable pair collections (e.g. |
| 32 | + ``[[(f0, x0), ...], ..., [(f, x), ...]]``). |
| 33 | + """ |
| 34 | + if isinstance(f_lists, Sequence): |
| 35 | + # TODO: Would be cool if this was lazily evaluated, no? |
| 36 | + # Seems like this whole thing would have to become a generator |
| 37 | + # function, though. |
| 38 | + ulos = () |
| 39 | + # ((f0, x0), ...), ((f, x), ...) |
| 40 | + for f_list in f_lists: |
| 41 | + f, args = car(f_list), cdr(f_list) |
| 42 | + _ulos = f(*(walk(a, s) for a in args)) |
| 43 | + ulos += _ulos |
| 44 | + if "use-maybe" not in _ulos: |
| 45 | + return ulos |
| 46 | + return ulos |
| 47 | + else: |
| 48 | + return () |
| 49 | + |
| 50 | + |
| 51 | +def condp(global_sugs: Tuple, branches: Union[Sequence, Mapping]): |
| 52 | + """A goal generator that produces a `conde`-like relation driven by |
| 53 | + suggestions potentially derived from partial miniKanren state values. |
| 54 | +
|
| 55 | + From [1]_. |
| 56 | +
|
| 57 | + Parameters |
| 58 | + ========== |
| 59 | + global_sugs |
| 60 | + A tuple containing tuples of suggestion functions and their |
| 61 | + logic variable arguments. Each suggestion function is evaluated |
| 62 | + using the reified versions of its corresponding logic variables (i.e. |
| 63 | + their "projected" values). Each suggestion function is expected to |
| 64 | + return a tuple of branch labels corresponding to the keys in |
| 65 | + `branches`. |
| 66 | + branches |
| 67 | + Sequence or mapping of string labels--for each branch in a conde-like |
| 68 | + goal--to a tuple of goals pairs. |
| 69 | +
|
| 70 | +
|
| 71 | + .. [1] Boskin, Benjamin Strahan, Weixi Ma, David Thrane Christiansen, and Daniel |
| 72 | + P. Friedman, "A Surprisingly Competitive Conditional Operator." |
| 73 | +
|
| 74 | + """ |
| 75 | + if isinstance(branches, Mapping): |
| 76 | + branches_: Sequence = tuple(branches.items()) |
| 77 | + else: |
| 78 | + branches_ = branches |
| 79 | + |
| 80 | + def _condp(s): |
| 81 | + global_los = collect(s, global_sugs) |
| 82 | + yield from ldisj_seq(lconj_seq(g) for k, g in branches_ if k in global_los)(s) |
| 83 | + |
| 84 | + return _condp |
| 85 | + |
| 86 | + |
| 87 | +def collectseq(branch_s: Mapping, f_lists: Optional[Sequence] = None): |
| 88 | + """A version of `collect` that takes a `dict` of branches-to-states. |
| 89 | +
|
| 90 | + Parameters |
| 91 | + ========== |
| 92 | + branch_s |
| 93 | + Branch labels to miniKanren state/replacements dictionaries. |
| 94 | + f_lists |
| 95 | + A collection of function + variable pair collections (e.g. |
| 96 | + ``[[(f0, x0), ...], ..., [(f, x), ...]]``). |
| 97 | + """ |
| 98 | + if isinstance(f_lists, Sequence): |
| 99 | + ulos = () |
| 100 | + for f_list in f_lists: |
| 101 | + f, args = f_list |
| 102 | + _ulos = f({k: reify(args, s) for k, s in branch_s.items()}) |
| 103 | + ulos += _ulos |
| 104 | + if "use-maybe" not in _ulos: |
| 105 | + return ulos |
| 106 | + return ulos |
| 107 | + else: |
| 108 | + return () |
| 109 | + |
| 110 | + |
| 111 | +def condpseq(branches: Union[Sequence[Sequence], Mapping]): |
| 112 | + r"""An experimental version of `condp` that passes branch-state-reified |
| 113 | + maps to branch-specific suggestion functions. |
| 114 | +
|
| 115 | + In other words, each branch-specific suggestion function is passed a `dict` |
| 116 | + with branch-label keys and the its function arguments are reified against |
| 117 | + the state resulting from said branch. |
| 118 | +
|
| 119 | + .. note:: |
| 120 | +
|
| 121 | + Only previously evaluated branches will show up in these `dict`\s, so |
| 122 | + branch order will determine the information available to each suggestion |
| 123 | + function. |
| 124 | +
|
| 125 | + Parameters |
| 126 | + ========== |
| 127 | + branches |
| 128 | + Ordered map or a sequence of sequences mapping string labels--for each |
| 129 | + branch in a `conde`-like goal--to a tuple starting with a single |
| 130 | + suggestion function followed by the branch goals. |
| 131 | +
|
| 132 | + """ |
| 133 | + if isinstance(branches, Mapping): |
| 134 | + branches_: Sequence = tuple(branches.items()) |
| 135 | + else: |
| 136 | + branches_ = branches |
| 137 | + |
| 138 | + def _condpseq(s, __bm=branches_): |
| 139 | + __bm, local_items = tee(__bm) |
| 140 | + |
| 141 | + # Provide each branch-specific suggestion function a copy of the state |
| 142 | + # after the preceding branch's goals have been evaluated. |
| 143 | + def f(items): |
| 144 | + los = set() |
| 145 | + branch_s = {} |
| 146 | + for k, goals_branch_sugs in local_items: |
| 147 | + # Branch suggestions can be `None` and all branch |
| 148 | + # goals will be added. |
| 149 | + branch_sugs = car(goals_branch_sugs) |
| 150 | + goals = cdr(goals_branch_sugs) |
| 151 | + |
| 152 | + if branch_sugs: |
| 153 | + # We only expect one suggestion function per-branch. |
| 154 | + branch_sugs = (branch_sugs,) |
| 155 | + los |= set(collectseq(branch_s or {k: s}, branch_sugs)) |
| 156 | + |
| 157 | + if branch_sugs is None or k in los: |
| 158 | + # TODO: Refactor! |
| 159 | + a, b = tee(lall(*goals)(s)) |
| 160 | + branch_s[k] = next(a) |
| 161 | + yield b |
| 162 | + |
| 163 | + branch_s.setdefault(k, None) |
| 164 | + |
| 165 | + yield from interleave(f(local_items)) |
| 166 | + |
| 167 | + return _condpseq |
0 commit comments