[m-rev.] for review: convert parse trees to strings

Julien Fischer jfischer at opturion.com
Wed Nov 1 21:41:00 AEDT 2023


On Wed, 1 Nov 2023, Zoltan Somogyi wrote:

> On 2023-11-01 17:59 +11:00 AEDT, "Julien Fischer" <jfischer at opturion.com> wrote:
>>> What prompted my question was the arrangement I have in mercury_json,
>>> where most of the work is subcontracted to (private) submodules.  That's
>>> not uncommon -- the examples in the compiler and term_io are just not
>>> large enough to warrent it. My suggestion would be that at minimum, the
>>> optimisation should apply to a module plus its child modules.
>>
>> All that said, the situation in mercury_json is more general than what
>> your proposal covers, in that it has:
>>
>> 1. Multiple class constraints on most of the predicates that would need
>>    to be specialised, typically these
>>
>>    stream.line_oriented(Stream, State),
>>    stream.unboxed_reader(Stream, char, State, Error),
>>    stream.putback(Stream, char, State, Error)
>>
>> 2. Some of the type variables are shared between constraints.
>>
>> 3. Some of the constraint arguments are ground.
>
> Your example exihibits 1 and 2, but not 3. What do you propose to do
> about/with ground args?

Good point, I suppose in that case you would only want the pragma
to match the constraints that have the ground arg.  So my example
below, should really look like:

    :- pragma type_spec_constrained_preds(
         [line_oriented(Stream, State),
          unboxed_reader(Stream, char, State, Error)
          putback(Stream, char, State, Error)],
         [Stream = io.text_output_stream,
          State = io.state, Error = io.error]).

And we would only specialise predicates whose constraints had
unboxed_readers and putback streams with their Unit set to ground.

>> Multiple constraints could be dealt with by making the first argument
>> a list, e.g.
>>
>>   :- pragma type_spec_constrained_preds(
>>        [line_oriented(Stream, State),
>>         unboxed_reader(Stream, Unit, State, Error)
>>         putback(Stream, Unit, State, Error)],
>>        [Stream = io.text_output_stream, Unit = char,
>>         State = io.state, Error = io.error]).
>>
>> (Indeed, by making the second arg. a list of of lists of type variable
>> asssigments, a user could request multiple specialisations in a single
>> pragma.)
>
> Ok. I see two issues there, one syntactic, one semantic.
>
> The syntactic issue is with the second argument. Having a list of
> substitions defined all at once is I think a good idea, but I wouldn't
> want to use a simple list of lists to represent it all. I would much rather
> use syntax such as (for the second arg only)
>
> [subst([Stream=..., Unit=..., ...]), subst([Stream=..., Unit=..., ...])]
>
> I think this conveys the intention of that argument significantly
> more clearly than a simple list of lists.

Sure.

> The semantic issue concerns the first arg. It seems obvious that
> for a predicate that has all the specified typeclass constraints
> in that specific order with that specific arrangement of variable sharing
> between them, we should apply the given specializations.
> It seems intuitive that we should do the same even if the order of
> the constraints on the pred/func does not match the order in the pragma.

Agreed, the order of constraints should not matter.

> But what should happen for predicates whose typeclass constraints specify
>
> - a nonempty but strict subset of the constraints in the first arg, and/or

For code that I have written or worked on: constraints are dropped as
you move down the call graph. To take the JSON library, for example, at
the top-level we have the three constraints above; at the leaves of the
call graph there is usually only a single constraint,
unboxed_reader(Stream, char, State, Error).

(Obviously, this could be avoided by having potentially redundant
constraints all the way down, but that seems undesirable.)

> - the variable sharing arrangement that is different than in the first arg?

I think if the variable sharing arrangement is different, then there
should not be match.

> I have no experience with code like that, but the implementation
> of the new pragma must decide what to do in each such case.
> You have much more experience with typeclass-heavy code,
> what do you think the semantics should be in these cases?

As above.

> With my original proposal, which included only a single typeclass
> constraint, the answer was fairly obvious: you apply to a pred or func
> all of the type specializations mentioned in the pragmas that apply to it,

Actually, I don't think the single typeclass constraint case is as obvious
as you think due to the presence of superclasses. Consider:

    :- typeclass class1(T) where ...

    :- typeclass class2(T) <= class1(T) where ...

    :- pred foo(T, ...) <= class2(T)

     foo(...) :-
       bar(...).

    :- pred bar(T, ...) <= class1(T).

In presence of

    :- pragma type_spec_constrained_preds(class2(T), [T = int]).

you would want the specialization to also apply to the predicate bar.
(And in my example here, the superclass hierarchy is only one level
deep, in general it could be more.)

Julien.


More information about the reviews mailing list