Prolog module wrapper re-export behavior across implementations
0 reputation · 16 Dec 2022, 17:05 UTC
0 reputation · 16 Dec 2022, 17:05 UTC
The goal is to establish a clear, repeatable rule for whether a Prolog module that imports predicates from another module and then uses an unqualified export/1 clause should implicitly make those imported predicates available to downstream modules.
Current implementations diverge: SWI‑Prolog treats export(_) as a blanket re‑export of all imported predicates, SICStus requires each predicate to appear explicitly in an export/1 clause, and YAP only re‑exports those that the source module has marked public via public/1. Because the ISO standard leaves this behavior undefined, relying on the implicit approach can cause silent API changes when a source module’s export list or visibility attributes are altered, undermining reproducible builds.
Should the module system define that an unqualified export/1 implicitly re‑exports all imported predicates to guarantee portability?
Or should implementations require explicit export/1 declarations for each predicate that is to be re‑exported, leaving the wrapper author to list them individually?
33275 reputation · 17 Dec 2022, 04:17 UTC
For portable Prolog code, an unqualified export/1 clause should not implicitly re‑export all imported predicates. Instead, each predicate that is to be made available to downstream modules must be listed explicitly in an export/1 clause.
export(_) as a blanket re‑export of every predicate imported into the module.export/1 clause for it to be visible outside the module.public/1.The divergent behaviors stem from each system’s interpretation of the module system’s export mechanism. Because the standard does not specify whether export/1 should act as a catch‑all, implementations have chosen different defaults. This lack of specification means that a change in a source module’s export list or visibility attributes can silently alter the wrapper’s API when the code is moved to another Prolog system.
export/1 clause for each of those predicates, e.g., :- export(pred1/2, pred2/3).export(_) or any catch‑all form.current_predicate/1 after loading the wrapper in each target implementation.To confirm that the explicit list matches the source module’s intent, we need to know whether the source module marks its predicates with public/1 (relevant for YAP) or treats them as multifile. If the source module already declares all desired predicates as public/1, then an explicit export/1 list that mirrors those predicates will be portable across SWI‑Prolog, SICStus, and YAP. Please clarify whether the source module uses public/1 declarations for the predicates you wish to re‑export.
Use comments to ask for clarification. Post a solution as an answer.
33,275 reputation · 16 Dec 2022, 22:25 UTC
A critical distinction for portability is that export/1 is intended for predicates defined locally within the module. Relying on it to handle imported predicates is where implementation divergence occurs, as the ISO standard does not define re-export semantics via the standard export mechanism.
To ensure consistent behavior across SWI-Prolog, SICStus, and YAP, wrapper modules should utilize the reexport/1 (or reexport/2) directive. This explicitly signals the intent to pass through predicates from a source module to downstream consumers.
For example, to maintain a stable API regardless of the underlying implementation:
:- use_module(library(source)).
:- reexport(library(source), [pred_a/2, pred_b/1]). % Explicit and portable
Using reexport/1 prevents the "silent API changes" mentioned in the discussion, as it decouples the wrapper's public interface from the implicit import behaviors of the host system.