[m-rev.] for review: restrict some typeclass definitions in .int{, 3} files to be abstract

Peter Wang novalazy at gmail.com
Wed Aug 2 13:24:21 AEST 2023


On Wed, 02 Aug 2023 11:53:41 +1000 "Zoltan Somogyi" <zoltan.somogyi at runbox.com> wrote:
> 
> On 2023-08-02 03:02 +02:00 CEST, "Peter Wang" <novalazy at gmail.com> wrote:
> > That looks fine.
> 
> Thank you.
> 
> >> +% XXX Including the above subtype definitions generates errors like these:
> >> +% term_conversion.m:199: In clause for predicate `term_to_univ_special_case'/6:
> >> +% term_conversion.m:199:   error: ambiguous overloading causes type ambiguity.
> >> +% term_conversion.m:199:   Possible type assignments include:
> >> +% term_conversion.m:199:   V_95:
> >> +% term_conversion.m:199:     `some [T] (list.list(T))'
> >> +% term_conversion.m:199:   or
> >> +% term_conversion.m:199:     `some [T] (list.non_empty_list(T))'
> >> +% UNDOC_PART_END
> > 
> > This occurs because [|]/2 is a constructor of both list(T) and
> > non_empty_list(T). The fix is to use explicit type qualification,
> > as below.
> 
> By definition, every constructor of a subtype occurs in its supertype,
> and it hasn't caused problems before. However, it does in this case.
> 

The ambiguity can be resolved usually, but not in this case.

I think where this problem will arise most in practice is when you try
to print out a term:

    print([1, 2, 3], !IO)

Is that a list(int) or non_empty_list(int)? The end result is the same,
but the compiler doesn't know that.

> The diff you attached works, and fixes the problem in the library directory.
> But in the compiler directory, we get over 400 lines of error messages.
> For one file, we get
> 
> error_type_util.m:214: In clause for function `higher_order_type_pieces'/5:
> error_type_util.m:214:   warning: highly ambiguous overloading.
> error_type_util.m:214:   The following symbols were overloaded in the following
> error_type_util.m:214:   contexts.
> error_type_util.m:199:   The function symbol `[]'/0.
> error_type_util.m:199:   The possible matches are:
> error_type_util.m:199:     the type constructor `list.empty_list'/1,
> error_type_util.m:199:     the type constructor `list.list'/1.
> error_type_util.m:200:   The function symbol `[]'/0 is also overloaded here.
> error_type_util.m:201:   The function symbol `[]'/0 is also overloaded here.
> error_type_util.m:206:   The function symbol `[]'/0 is also overloaded here.
> error_type_util.m:208:   The function symbol `[]'/0 is also overloaded here.
> error_type_util.m:209:   The function symbol `[]'/0 is also overloaded here.
> error_type_util.m:210:   The function symbol `[]'/0 is also overloaded here.
> error_type_util.m:211:   The function symbol `[]'/0 is also overloaded here.
> error_type_util.m:212:   The function symbol `[]'/0 is also overloaded here.
> error_type_util.m:200:   The function symbol `[|]'/2.
> error_type_util.m:200:   The possible matches are:
> error_type_util.m:200:     the type constructor `list.list'/1,
> error_type_util.m:200:     the type constructor `list.non_empty_list'/1.
> error_type_util.m:201:   The function symbol `[|]'/2 is also overloaded here.
> error_type_util.m:197:   The function symbol `higher_order_type'/5.
> error_type_util.m:197:   The possible matches are:
> error_type_util.m:197:     the type constructor
> error_type_util.m:197:     `parse_tree.prog_data.mer_type'/0,
> error_type_util.m:197:     the type constructor
> error_type_util.m:197:     `parse_tree.prog_type.non_kinded_type'/0.
> error_type_util.m:200:   The function symbol `words'/1.
> error_type_util.m:200:   The possible matches are:
> error_type_util.m:200:     the type constructor
> error_type_util.m:200:     `parse_tree.error_spec.format_piece'/0,
> error_type_util.m:200:     function `string.words'/1.
> error_type_util.m:201:   The function symbol `words'/1 is also overloaded here.
> error_type_util.m:205:   The function symbol `list.map'/2.
> error_type_util.m:205:   The possible matches are:
> error_type_util.m:205:     predicate `list.map'/3,
> error_type_util.m:205:     function `list.map'/2.
> 

These ambiguities can be resolved, but the compiler warns about the
predicate using a lot of overloaded symbols. Maybe we should raise the
default value of --typecheck-ambiguity-warn-limit.

> > Perhaps we can point out which lines the terms with ambiguous types
> > occur on.
> 
> As shown above, this already happens. (Though if e.g. [] occurs
> more than once on a line, you get just one message for that line.)
> 

In term_conversion.m, it took me some trial and error to find the
location where I should place the type qualifiers.

> I am guessing that subtype checking is not treating subtypes of lists
> any differently than subtypes of other types, which was my first thought,
> and that instead, the factor that distinguishes the errors we get for empty_list
> and non_empty_list is that there are clauses that contain a *lot* of occurrences
> of [] and [|]. When every such occurrence doubles the number of type
> assignments in the type checker, we soon exceed the limit. Do you
> agree with this diagnosis?

Yes.

> If yes, then I want to keep the definitions of empy_list and non_empty_list
> commented out, but with the comment updated to reflect this diagnosis.
> The reason for that is that I don't like the idea of either us, or other Mercury
> programmers, having to go around adding explicit type qualifications to lots
> of nils and cons just to get the number of type assignments in the type checker
> below the ambiguity threshold.
> 
> It would be nice if the typechecker could handle subtypes *without*
> this kind of doubling. Do you know of  any way, preferably a simple way,
> to accomplist that? One idea is that when a var is bound to e.g. [],
> then instead of recording its type as list and empty_list in two separate
> type assignments, record it as just empty_list, but with a marker that
> effectively says "or any supertype". Then, if it ever occurs in a context
> that needs it to be list, then just change its type to list in that type assignment,
> since list is a supertype of empty_list. This update wouldn't happen without
> that marker. Would this be a viable approach, do you think?

That does seem worth investigating.

Some initial thoughts:

- At the end, we must remember to check that all program variables are
  assigned a proper type, so there are no more "or any supertype" left.

- The "marker" would probably be placed in the type bindings map of the
  type_assign structure.

- When a variable is to be assigned a subtype-or-any-supertype,
  we should introduce a type variable, then bind the type variable to
  that subtype-or-any-supertype.

  This should deal with the problem of var-var unifications.
  If X = Y, then X and Y should always end up with the same type.

Peter


More information about the reviews mailing list