[m-users.] Document "Module typeclasses should be instantiated in the interface"

fabrice nicol fabrnicol at gmail.com
Fri May 14 20:59:36 AEST 2021


In other words, building mod2 as a library should not be possible
>> without an error or at least a warning.
> Ok, so it would be a warning like --warn-dead-preds but the analysis 
> would be much more difficult.

You phrased the intuition underlying my 'pragmatic user' remark. A 
library typeclass predicate should not be allowed to compiled into a 
library if there is no type to satisfy the typeclass constraint.

> I don't know how difficult, but just for thought: :- module m1. :- 
> interface. :- typeclass tc(T) where [ pred eval(T::in, int::out) is 
> det ]. :- type foo ---> some [T] foo(T) => tc(T). :- func make_foo = 
> foo. :- pred eval_foo(foo::in, int::out) is det. 
> %-------------------------------------------% :- implementation. :- 
> instance tc(int) where [ eval(I, I) ]. make_foo = 'new foo'(42). 
> eval_foo(Foo, I) :- Foo = foo(X), eval(X, I). It's plausible that 
> because: (1) make_foo is exported (2) make_foo returns a foo (3) foo 
> has a class constraint tc(T) (4) the make_foo implementation returns a 
> foo where the existentially quantified type variable T = int then we 
> could deduce that 'instance tc(int)' is usable, but it's highly 
> unlikely anyone will implement such a thing. Peter

Stimulated piece of coding.

If I understand you well, there might be convoluted cases in which an 
exported typeclass, without an abstract instance in the interface, could 
probably be used in a parent file if the type instantiation is somehow 
bootstrapped (or 'lifted up') by existential quantification plus symbol 
export in the interface of the module. But (reassuringly?) this does not 
work. Yes, your module m1 does build into a library, but the ominous 
"unsatisfiable typeclass contraint" error message pops up again if you 
try to import module m1 into a parent file and use 'eval' like this:

    :- module test4.
    :- interface.
    :- import_module io.
    :- pred main(io::di, io::uo) is det.

    :- implementation.

    :- import_module int, string, list.
    :- import_module m1.

    main(!IO) :-
         eval(42, I),
         io.format("this int: %d\n", [i(I)], !IO).

Building your m1 module (as-is) and test4, using DEV compiler (at HEAD), 
with:

    /usr/local/mercury-DEV/bin/mmc  -E --make libm1

    /usr/local/mercury-DEV/bin/mmc -E --make test4

I obtain:

    test4.m:014: In clause for predicate `main'/2:
    test4.m:014:   unsatisfiable typeclass constraint:
    test4.m:014: `m1.tc(int)'.
    ** Error making `Mercury/cs/test4.c'.

So after all, implementing something like a 
'--no-unsatisfiable-typeclass' warning option in the compiler may well 
be easier than expected.

Coming back to my first post code, I now realize I have to slightly 
correct my remark.

An error message cannot and should not be issued at library compile time 
as it is correct Mercury syntax (and good common sense) to satisfy the 
typeclass instantiation constraint anywhere else than in module mod2. So 
it is not strictly necessary for an abstract instance declaration to be 
present in the **same** module as the typeclass declaration.

E.g., the following builds and runs fine:

    %------- file mod3.m -----------------------------%

    :- module mod3.
    :- interface.
    :- import_module string.

    :- typeclass eval_type(T) where [
         pred eval(T),
         mode eval(out) is det
    ].

    %------- file mod4.m -----------------------------%

    :- module mod4.
    :- interface.
    :- import_module mod3.

    :- instance eval_type(string).

    :- implementation.

    :- instance eval_type(string) where [
            pred(eval/1) is eval_string
        ].

    :- pred eval_string(string::out) is det.

    eval_string(Result) :- Result = "a".

    %------ file test4.m ----------------------------%

    :- module test4.
    :- interface.
    :- import_module io.
    :- pred main(io::di, io::uo) is det.

    :- implementation.

    :- import_module string, list.
    :- import_module mod3, mod4.

    main(!IO) :-
         eval(S),
         io.format("this string: %s\n", [s(S)], !IO).
    %------------------------------------------------%

So, the 'pragmatic' improvement that I am advocating could not go beyond 
a warning message at library compile time.

Alternatively, it could be implemented in the linker after all the 
dependency graph has been resolved, if it is found that there is a 
typeclass with no one type to satisfy it.

Fabrice.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mercurylang.org/archives/users/attachments/20210514/de85635d/attachment.html>


More information about the users mailing list