ciao-lang / ciao

Ciao is a modern Prolog implementation that builds up from a logic-based simple kernel designed to be portable, extensible, and modular.
https://ciao-lang.org
GNU Lesser General Public License v3.0
268 stars 20 forks source link

order in predicate visibility rules #63

Open jfmc opened 2 years ago

jfmc commented 2 years ago

Note that predicates are never overridden, so the problem may be in the predicate visibility rules.

:- module(a, [foo/1]).
foo(a).
:- module(b, [foo/1]).
foo(b).
?- use_module(a).

yes
?- foo(X).

X = a ? 

yes
?- use_module(b).

yes
?- foo(X).

X = a ? 

yes

As @mherme says both a:foo(X) and b:foo(X) work, but module visibility rules in Ciao resolves foo(X) as a:foo(X). Perhaps b:foo(X) would be more natural? Changing it is not trivial but can do it, specially if this is expected in other languages.

Originally posted by @jfmc in https://github.com/ciao-lang/ciao/issues/61#issuecomment-1214754013

Jean-Luc-Picard-2021 commented 2 years ago

SWI-Prolog throws an error:

% a.pl compiled into a 0.00 sec, 1 clauses
ERROR: [Thread pce] import/1: No permission to import b:foo/1 into user (already imported from a)
% b.pl compiled into b 0.02 sec, 1 clauses
Jean-Luc-Picard-2021 commented 2 years ago

On the other hand SWI-Prolog allows this overriding:

:- module(c, [foo/1]).
:- use_module(a).
foo(c).

But a little annoyingly it gives a warning:

%  a compiled into a 0.00 sec, 1 clauses
Warning: c:/users/rburs/desktop/rr/c.pl:3:
Warning:    Local definition of c:foo/1 overrides weak import from a
% c.pl compiled into c 0.00 sec, 2 clauses
?- foo(X).
X = c.

You can switch off the warning, by using use_module/2 with except/1:

:- module(c, [foo/1]).
:- use_module(a, except([foo/1])).
foo(c).

Now the warning goes away:

%  a compiled into a 0.00 sec, 1 clauses
% c.pl compiled into c 0.00 sec, 2 clauses
?- foo(X).
X = c.
jfmc commented 2 years ago

Actually I was interested in the behavior of other languages: a.py:

def foo():
  return "a"

b.py:

def foo():
  return "b"

c.py:

from a import * 
from b import * 
print(foo())
$ python c.py
b

(if I'm not confused) Python resolves names in LIFO while Ciao does in FIFO. Rather than errors, we show warnings if there are conflicts. Python is silent. SWI gives errors. JavaScript ES6 modules forbid 'import *' to avoid problems.

At least for the Ciao toplevel it would make sense to adopt LIFO.

Jean-Luc-Picard-2021 commented 2 years ago

What does the ISO module standard say?

Unbenannt2

But I agree there are alternatives around, being more tollerant. What some Programming languages then use is for example this:

C3 linearization https://en.wikipedia.org/wiki/C3_linearization

You might not see it in the simple example you are dealing with.

pmoura commented 2 years ago

(if I'm not confused) Python resolves names in LIFO while Ciao does in FIFO. Rather than errors, we show warnings if there are conflicts. Python is silent. SWI gives errors. JavaScript ES6 modules forbid 'import *' to avoid problems.

JS ES6, like Logtalk, is the only one in your list above that provides a sensible solution. For Prolog, that means deprecating/killing the use_module/1 directive (or, as in Logtalk, re-purpose it only for declaring module aliases).

jfmc commented 2 years ago

Thanks @pmoura! I've checked your documentation and everything looks really reasonable. Something that we really miss in Ciao is a use_module/1 that enforces module qualification. We are not sure about killing use_module/1 (sometimes it is useful at least for legacy code or in controlled places). We had in the TODO list but never though about a syntax to write it. It is good to know that this is (more or less) consistent with your design in Logtalk.

Is there any doc/paper that explains it in more detail (e.g., describing other languages with the same decision)?

pmoura commented 2 years ago

As you may have noticed in the documentation, Logtalk's use_module/1 directive takes a list as argument. That provides a simply solution to distinguish it during compilation from the Prolog use_module/1 directive.

The dangers of directives like the Prolog use_module/1 directive are well know and I have seen it discussed in several programming languages resources. One of the main problems with "just import everything" directives is that a working application today can easily become a broken application tomorrow when new predicates are added to a library that, necessarily, the application doesn't use but that cause a conflict (if you're lucky) with other libraries (which may be private to the application and thus invisible to the developers updating the libraries).

mherme commented 2 years ago

I agree. On the other hand many of those languages are meant only for building large applications, and not for prototyping. Prolog in its original form is really good for prototyping, and in its more modern incarnations (with modules, etc.) it is really great in that it allows traveling a smooth path from prototyping to production-strength code. Ciao in particular provides a lot of things to help this transition. For example you can write:

:- module(_,_).

which exports everything and takes as module name the name of the file. This is as flexible for prototyping as a user file, but it is scoped, i.e., you know all the code for this module is right here, and cannot be loaded in another place (as happens with at least traditional user files). This is very useful for detecting errors (undefined predicates are just an example), so that it actually improves prototyping. Getting back to use_module, being able to 'import everything' is admittedly more dangerous than not allowing it, but also much more flexible, and it is part of this ability to travel the path from prototyping to production-strength.

jfmc commented 2 years ago

Thanks @mherme and @pmoura! Let me summarize:

pmoura commented 2 years ago

Note that you can always do as in Logtalk: if the use_module/1 directive argument is a list, then it's declaring module aliases or modules that are being imported but require the use of explicit qualification. If not, it's the current/traditional use_module/1 directive.