# Replace\_unifies

**URL:** <https://swi-prolog.discourse.group/t/replace-unifies/4114>\
**Category:** Help!\
**Created:** [June 28, 2021, 10:57am UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114 "2021-06-28T10:57:41Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![anniepoo](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/anniepoo/32/12_2.png) [@anniepoo](https://swi-prolog.discourse.group/u/anniepoo)\
**Post date:** [June 28, 2021, 10:57am UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/1 "2021-06-28T10:57:41Z")

</div>

I was writing some code for a game. It handles geometry, so there’s lots of numeric constants. It was fairly obvious this was the road to hell.

Not wanting to dig constants out of a fact, I decided to use $cell\_width type constants, so quickly banged this out.

```prolog
:- module(symbolic_constant,
          [
              sym/2
          ]).

:- dynamic symbolic_constant/2.

sym(Name, Val) :-
    asserta(symbolic_constant(Name, Val)).

expand_constants(In, In) :-
    var(In),
    !.
expand_constants($In, Out) :-
    symbolic_constant(In, Out).
expand_constants($In, _Out) :-
    \+ symbolic_constant(In, _),
    throw(format('no such constant'-[In])).
expand_constants(In, In) :-
    atomic(In).
expand_constants(In, Out) :-
    is_dict(In),
    dict_pairs(In, Tag, Pairs),
    maplist(expand_pair, Pairs, PairsOut),
    dict_pairs(Out, Tag, PairsOut),
    !.
expand_constants(In, Out) :-
    compound(In),
    In =.. InList,
    maplist(expand_constants, InList, OutList),
    Out =.. OutList.
expand_constants(In, Out) :-
    is_list(In),
    maplist(expand_constants, In, Out).

expand_pair(K-V, KO-VO) :-
    expand_constants(K, KO),
    expand_constants(V, VO).

user:term_expansion(In, Out) :-
    expand_constants(In, Out).

```

very dissatisfied. Is there a more robust way to do this, preferably a system predicate?

Ideally there would be some general map\_term that takes each sub\_term and applies some transform to it. I can trivially convert the above to such a thing, but I’m wondering why there isn’t a library predicate that does this. Am I missing it? I end up doing this about every 6 months.

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [June 28, 2021, 11:15am UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/2 "2021-06-28T11:15:08Z")

</div>

> [@anniepoo](#):
>
> why there isn’t a library predicate that does this. Am I missing it?

Since recently (after a similar discussion), mapargs/3 🙂

---

<div class="post-metadata">

**Author:** ![anniepoo](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/anniepoo/32/12_2.png) [@anniepoo](https://swi-prolog.discourse.group/u/anniepoo)\
**Post date:** [June 29, 2021, 10:09am UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/3 "2021-06-29T10:09:09Z")

</div>

Can’t see how mapargs/3 is relevant here.

This just feels like a general pattern. Last time I needed it I needed to do XSLT’s job - I had XML and wanted to replace some pattern with another one.

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [June 29, 2021, 11:49am UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/4 "2021-06-29T11:49:18Z")

</div>

Something like this:

```prolog
:- use_module(library(terms)).

map_symbols(TermIn, TermOut) :-
    mapargs(map, TermIn, TermOut).

map($X, Val) =>
    ( symbol(X, Val0)
    -> Val = Val0
    ; existence_error(symbol, X)
    ).
map(X, Val) => Val = X.

symbol(pi, 3.14).

```

And then:

```
?- map_symbols(x($pi), T).
T = x(3.14).
```

---

<div class="post-metadata">

**Author:** ![grossdan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/grossdan/32/23_2.png) [@grossdan](https://swi-prolog.discourse.group/u/grossdan)\
**Post date:** [June 29, 2021, 11:51am UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/5 "2021-06-29T11:51:51Z")

</div>

Can this be used for term expansion as well – as a way to introduce constants …

Also, what does map\_symbols/2 do – it isn’t called from map/2

---

<div class="post-metadata">

**Author:** ![anniepoo](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/anniepoo/32/12_2.png) [@anniepoo](https://swi-prolog.discourse.group/u/anniepoo)\
**Post date:** [June 29, 2021, 12:15pm UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/6 "2021-06-29T12:15:26Z")

</div>

Jan - I’m doing

`foo(7.14, blah(_{barg: $pi}, _), [1, 3, mep($tau)])` goes to  
`foo(7.14, blah(_{barg:3.14}, _), [1, 3, mep(6.28)])`

and more generally, would like to do replace all patterns that match using a lambda

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [June 29, 2021, 1:13pm UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/7 "2021-06-29T13:13:34Z")

</div>

> [@grossdan](#):
>
> what does map\_symbols/2 do – it isn’t called from map/2

Sorry, example called wrong predicate. Edited.

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [June 29, 2021, 1:20pm UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/8 "2021-06-29T13:20:34Z")

</div>

Oops, I thought mapargs/3 works recursively. Sorry. I’ve just added mapsubterms/3 that does the job recursively. Now your call works with

```prolog
:- use_module(library(terms)).

map_symbols(TermIn, TermOut) :-
    mapsubterms(map, TermIn, TermOut).

map($(X), Val), atom(X) =>
    ( symbol(X, Val0)
    -> Val = Val0
    ; existence_error(symbol, X)
    ).
map(_, _) =>
    fail.

symbol(pi, 3.14).
symbol(tau, 6.28).

```

Docs for the new predicate are below. Details may change depending on feedback, e.g.

- Better name? I deliberately omitted the underscore are none of the mapping predicates use this.
- Should we call the mapper on variables?
- ???

–

**mapsubterms(_:Goal, +Term1, -Term2_) is det.**

Recursively map sub terms of Term1 into subterms of Term2 for every pair for which call(Goal, ST1, ST2) succeeds. Procedurably, the mapping for each (sub) term pair `T1/T2` is defined as:

- If `T1` is a variable, Unify `T2` with `T1`.
- If call(Goal, T1, T2) succeeds we are done. Note that the mapping does not continue in `T2`. If this is desired, `Goal` must call mapsubterms/3 explicitly as part of it conversion.
- If `T1` is a dict, map all values, i.e., the _tag_ and _keys_ are left untouched.
- If `T1` is a list, map all elements, i.e., the list structure is left untouched.
- If `T1` is a compound, use same\_functor/3 to instantiate `T2` and recurse over the term arguments left to right.
- Otherwise `T2` is unified with `T1`.

---

<div class="post-metadata">

**Author:** ![anniepoo](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/anniepoo/32/12_2.png) [@anniepoo](https://swi-prolog.discourse.group/u/anniepoo)\
**Post date:** [June 29, 2021, 2:05pm UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/9 "2021-06-29T14:05:00Z")

</div>

yay! much better to have it in the library. I didn’t like my roll yer own

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [June 29, 2021, 2:44pm UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/10 "2021-06-29T14:44:50Z")

</div>

Depending on your use-case, perhaps destructive assignment to dicts? (b\_set\_dict/3).

But there doesn’t seem to be a `mapdict`, although it’s easy to roll your own with dict\_pairs/2, maplist/3, and the various predicates that operate on pairs.

---

<div class="post-metadata">

**Author:** ![anniepoo](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/anniepoo/32/12_2.png) [@anniepoo](https://swi-prolog.discourse.group/u/anniepoo)\
**Post date:** [June 29, 2021, 2:46pm UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/11 "2021-06-29T14:46:18Z")

</div>

not destructive.

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [June 29, 2021, 3:23pm UTC](https://swi-prolog.discourse.group/t/replace-unifies/4114/12 "2021-06-29T15:23:37Z")

</div>

The assignment is undone on backtracking, if that’s what you mean. (And the key must already be in the dict)

```prolog
?- D=d{a:1,b:2}, (b_set_dict(a,D,555), writeln(D), fail ; writeln(D)).
d{a:555,b:2}
d{a:1,b:2}
D = d{a:1, b:2}.

```
