atom_concat/3 is forward-only: split/backward modes raise type_error instead of enumerating decompositions #35

Closed
opened 2026-06-23 21:03:36 +00:00 by navicore · 1 comment
Owner

Summary

atom_concat/3 works only in the forward (assembly) direction, where the first
two arguments are bound. The standard backward / split modes — third argument
bound, one or both of the first two unbound — are not supported. Instead of
backtracking through decompositions (as ISO requires) they raise an error, and
the error class is itself wrong for an unbound argument.

Version

plgc 0.3.1.

Reproduction

Given any program file p.pl (e.g. p.):

Forward (works):

$ plgc run p.pl --query "atom_concat(foo, bar, X)" --format text
X = foobar

Backward / split (all error):

$ plgc run p.pl --query "atom_concat(X, bar, foobar)" --format text
Error: Runtime error: error(type_error(atom, _0), atom_concat/3: first two arguments must be atoms)

$ plgc run p.pl --query "atom_concat(foo, Y, foobar)" --format text
Error: Runtime error: error(type_error(atom, _0), atom_concat/3: first two arguments must be atoms)

$ plgc run p.pl --query "atom_concat(A, B, abc)" --format text
Error: Runtime error: error(type_error(atom, _0), atom_concat/3: first two arguments must be atoms)

Expected (ISO 8.16.2)

Two distinct deviations:

1. Missing split mode (primary). When Atom_3 is instantiated and Atom_1 /
Atom_2 are unbound, atom_concat/3 is nondeterministic and must enumerate every
decomposition. For example atom_concat(A, B, abc) should yield, on backtracking:

A B
'' abc
a bc
ab c
abc ''

and the partially-bound forms should pick out the matching split:
atom_concat(X, bar, foobar)X = foo; atom_concat(foo, Y, foobar)
Y = bar. This is the same relational / multi-mode behaviour append/3 already
has for lists.

2. Wrong error class (secondary). Even setting the split mode aside, raising
type_error(atom, _Var) for an unbound argument is non-conformant: ISO reserves
type_error for a bound term of the wrong type. In the genuine error modes
(e.g. Atom_1 unbound and Atom_3 unbound) the standard prescribes
instantiation_error, not type_error.

Contrast — the other text builtins are bidirectional on 0.3.1

?- atom_chars(X, [d, o, g]).      X = dog
?- number_chars(N, ['4', '2']).   N = 42

Only atom_concat/3 is forward-only.

Notes / impact

Found while scoping the loglings "atoms & text" chapter. atom_concat is fully
usable for assembly (building qualified names, labels, etc.), but cannot be
taught as a relational splitter the way append/3 is. Filing so the split mode
— and the instantiation_error correction — can be considered.

## Summary `atom_concat/3` works only in the forward (assembly) direction, where the first two arguments are bound. The standard **backward / split** modes — third argument bound, one or both of the first two unbound — are not supported. Instead of backtracking through decompositions (as ISO requires) they raise an error, and the error class is itself wrong for an unbound argument. ## Version `plgc 0.3.1`. ## Reproduction Given any program file `p.pl` (e.g. `p.`): **Forward (works):** ``` $ plgc run p.pl --query "atom_concat(foo, bar, X)" --format text X = foobar ``` **Backward / split (all error):** ``` $ plgc run p.pl --query "atom_concat(X, bar, foobar)" --format text Error: Runtime error: error(type_error(atom, _0), atom_concat/3: first two arguments must be atoms) $ plgc run p.pl --query "atom_concat(foo, Y, foobar)" --format text Error: Runtime error: error(type_error(atom, _0), atom_concat/3: first two arguments must be atoms) $ plgc run p.pl --query "atom_concat(A, B, abc)" --format text Error: Runtime error: error(type_error(atom, _0), atom_concat/3: first two arguments must be atoms) ``` ## Expected (ISO 8.16.2) Two distinct deviations: **1. Missing split mode (primary).** When `Atom_3` is instantiated and `Atom_1` / `Atom_2` are unbound, `atom_concat/3` is nondeterministic and must enumerate every decomposition. For example `atom_concat(A, B, abc)` should yield, on backtracking: | A | B | |---|---| | `''` | `abc` | | `a` | `bc` | | `ab` | `c` | | `abc` | `''` | and the partially-bound forms should pick out the matching split: `atom_concat(X, bar, foobar)` → `X = foo`; `atom_concat(foo, Y, foobar)` → `Y = bar`. This is the same relational / multi-mode behaviour `append/3` already has for lists. **2. Wrong error class (secondary).** Even setting the split mode aside, raising `type_error(atom, _Var)` for an *unbound* argument is non-conformant: ISO reserves `type_error` for a bound term of the wrong type. In the genuine error modes (e.g. `Atom_1` unbound *and* `Atom_3` unbound) the standard prescribes `instantiation_error`, not `type_error`. ## Contrast — the other text builtins are bidirectional on 0.3.1 ``` ?- atom_chars(X, [d, o, g]). X = dog ?- number_chars(N, ['4', '2']). N = 42 ``` Only `atom_concat/3` is forward-only. ## Notes / impact Found while scoping the loglings "atoms & text" chapter. `atom_concat` is fully usable for *assembly* (building qualified names, labels, etc.), but cannot be taught as a relational splitter the way `append/3` is. Filing so the split mode — and the `instantiation_error` correction — can be considered.
Author
Owner
https://git.navicore.tech/navicore/patch-prolog/commit/1b019bdc9a6ec7694624c8b05b27816ad8aef5cf
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
navicore/patch-prolog#35
No description provided.